using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.IO; using System.Xml.Serialization; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using QURO.Animation; using QURO; using System.Xml; using AnimationEditor.Modifications; using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate; namespace AnimationEditor { public partial class Form1 : Form { private History undoHistory; private Texture2D _spriteSheet; //property ensures animationPreview holds a copy of whatever spriteSheet the editor is using private Texture2D spriteSheet { get { return _spriteSheet; } set { _spriteSheet = value; animationPreview.SpriteSheet = value; } } private int frameRate; private BindingList spriteMap; private BindingList frames; private BindingList frameSprites; private BindingList animations; private Animation currentAnimation; private Frame currentFrame; private bool frameNameBoxEdited; private bool tracking; private bool reading; private float importDelay = 0; public Form1() { InitializeComponent(); undoHistory = new History(); frameRate = 60; importDelayBox.Value = 0; animationPreview.FrameChanged += animationPreview_FrameChanged; animationPreview.AnimationEnded += animationPreview_AnimationEnded; animationPreview.SpriteDragUpdate += animationPreview_SpriteDragUpdate; animationPreview.SpriteDragCompleted += animationPreview_SpriteDragComplete; } private void DoModification(IModification mod) { ModHelper.DoModificationWithSelectionTracking(mod, animationBox, frameListBox, frameSpriteListBox); undoHistory.Add(mod); } private void animationPreview_SpriteDragComplete(object sender, EventArgs e) { var args = e as SpriteDragArgs; var mod = new MoveSprite(frameSprites, frameSpriteListBox, args.DragDistance); mod.Undo(); DoModification(mod); } private void frameListBox_SetSingleSelection(int index) { frameListBox.SelectedIndices.Clear(); frameListBox.SelectedIndex = index; } private void EnableMapAndAnimationLoadingControls() { loadSpriteMapToolStripMenuItem.Enabled = true; loadAnimationToolStripMenuItem.Enabled = true; loadAnimationSetToolStripMenuItem.Enabled = true; } private void EnableAnimationEditingControls() { saveAnimationToolStripMenuItem.Enabled = true; saveAnimationSetToolStripMenuItem.Enabled = true; editToolStripMenuItem.Enabled = true; updateAnimationSpritesFromSpriteMapToolStripMenuItem.Enabled = true; frameEditorPanel.Enabled = true; animationPanel.Enabled = true; } private void InitiateAnimationList(bool newAnimation = true) { if (newAnimation) animations = new BindingList() { new Animation() }; else animations = new BindingList(); animationBox.DataSource = animations; animationBox.DisplayMember = "Name"; } private void addSpriteToFrameListButton_Click(object sender, EventArgs e) { if (spriteMapListBox.SelectedItem != null) { var spriteToAdd = spriteMap[spriteMapListBox.SelectedIndex].Clone(); AddFrame(new Frame(spriteToAdd, importDelay)); } } private void AddFrame(Frame frameToAdd) { DoModification(new AddFrame(frames, frameListBox, frameToAdd)); UpdateAnimation(); } private void UpdateAnimation() { if(frames?.Count > 0) { //update currentAnimation's data if(currentAnimation == null) currentAnimation = new Animation("unnamed", frames.ToArray(), true); else { currentAnimation.Name = animationNameBox.Text; currentAnimation.Frames = frames.ToArray(); currentAnimation.IsLooping = animationLoopCheckBox.Checked; } //ensure frameTrackBar length matches current animation frameTrackBar.Maximum = currentAnimation.Frames.Count() - 1; //send currentAnimation to animationPreview if (animationPreview.PreviewSprite == null) animationPreview.PreviewSprite = new AnimatedSprite(currentAnimation); else animationPreview.PreviewSprite.CurrentAnimation = currentAnimation; //restore currently viewed frame if (frameListBox.SelectedIndices.Count < 2) frameListBox_SetSingleSelection(frameTrackBar.Value); else frameListBox.SelectedIndex = frameTrackBar.Value; //if animations list is empty, send created currentAnimation to it if(animations.Count < 1) { animations.Add(currentAnimation); animationBox.DataSource = animations; animationBox.DisplayMember = "Name"; } } else { animationPreview.PreviewSprite = null; } } private void UpdateEnabledControls() { bool singleFrameSelected = frameListBox.SelectedIndices.Count < 2; bool singleSpriteSelected = frameSpriteListBox.SelectedIndices.Count < 2; //update per-frame controls frameSpritePanel.Enabled = singleFrameSelected; moveFrameDownButton.Enabled = singleFrameSelected; moveFrameUpButton.Enabled = singleFrameSelected; removeFrameButton.Enabled = singleFrameSelected; duplicateFrameButton.Enabled = singleFrameSelected; //update per-sprite controls spritePosPanel.Enabled = singleSpriteSelected; replaceSpriteButton.Enabled = singleSpriteSelected && spriteMap != null; moveSpriteDownButton.Enabled = singleSpriteSelected; moveSpriteUpButton.Enabled = singleSpriteSelected; removeSpriteButton.Enabled = singleSpriteSelected; reading = true; if (!singleSpriteSelected) { spriteXPosBox.Text = ""; spriteYPosBox.Text = ""; } else { spriteXPosBox.Text = spriteXPosBox.Value.ToString(); spriteYPosBox.Text = spriteYPosBox.Value.ToString(); } reading = false; } private void ReadAnimationInfo() { reading = true; frameTrackBar.Maximum = currentAnimation.Frames.Count() - 1; frames = new BindingList(currentAnimation.Frames.ToList()); frameListBox.DataSource = frames; frameListBox.DisplayMember = "Name"; animationNameBox.Text = currentAnimation.Name; animationLoopCheckBox.Checked = currentAnimation.IsLooping; reading = false; } private void spriteListBox_SelectedIndexChanged(object sender, EventArgs e) { if (spriteMapListBox.SelectedItem != null) { var currentSprite = (Sprite)spriteMapListBox.SelectedItem; Stream texture2DToImageStream = new MemoryStream(); Texture2DRegionExtractor.GetTexture2DFromRegion(spriteSheet, spriteSheet.GraphicsDevice, currentSprite.Bounds) .SaveAsPng(texture2DToImageStream, currentSprite.Bounds.Width, currentSprite.Bounds.Height); spritePreview.Image = Image.FromStream(texture2DToImageStream); texture2DToImageStream.Close(); } } private void frameListBox_SelectedIndexChanged(object sender, EventArgs e) { if(!tracking) UpdateEnabledControls(); if (frameListBox.SelectedIndices.Count == 1 && frameListBox.SelectedItem != null) { //create copy of current frameSprite selection List oldFrameSpriteSelection = new List(); foreach (int index in frameSpriteListBox.SelectedIndices) { oldFrameSpriteSelection.Add(index); } //update currentFrame to match selection currentFrame = (Frame)frameListBox.SelectedItem; //update preview and frameTrackBar frame positions if (animationPreview.PreviewSprite != null && !animationPreview.Playing) { animationPreview.PreviewSprite.CurrentFrameIndex = frameListBox.SelectedIndex; frameTrackBar.Maximum = frames.Count - 1; frameTrackBar.Value = frameListBox.SelectedIndex; } //update frameSpriteListBox with the current frame's sprites if (currentFrame.Sprites != null) frameSprites = new BindingList(currentFrame.Sprites); else frameSprites = new BindingList(); frameSpriteListBox.DataSource = frameSprites; frameSpriteListBox.DisplayMember = "Name"; //restore old frameSprite selection frameSpriteListBox.SelectedIndices.Clear(); foreach(int index in oldFrameSpriteSelection) { if(index < frameSprites.Count) { frameSpriteListBox.SelectedIndex = index; } } //ensure at least one sprite is selected if (frameSprites.Count > 0 && frameSpriteListBox.SelectedIndices.Count < 1) frameSpriteListBox.SelectedIndex = 0; //update forms fields to match selected frame's informations UpdateFrameInformation(); } else if (frameListBox.SelectedIndices.Count > 1) { //disable preview sprite drag+drop functionality if (animationPreview.EditSprites != null) animationPreview.EditSprites.Clear(); } } private void UpdateFrameInformation() { reading = true; delayInputBox.Value = (int)(currentFrame.Delay * frameRate); frameNameBox.Text = currentFrame.Name; reading = false; } private void delayInputBox_ValueChanged(object sender, EventArgs e) { if (reading || tracking) return; DoModification(new ChangeFrameDelay(frames, frameListBox, (float)delayInputBox.Value / frameRate)); } private void loadSpriteSheetToolStripMenuItem_Click(object sender, EventArgs e) { if (loadSpriteSheetDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { FileStream fileStream = new FileStream(loadSpriteSheetDialog.FileName, FileMode.Open); spriteSheet = Texture2D.FromStream(animationPreview.Editor.graphics, fileStream); EnableMapAndAnimationLoadingControls(); fileStream.Close(); } } private void loadSpriteMapToolStripMenuItem_Click(object sender, EventArgs e) { LoadSpriteMapFromFile(); } private Dictionary LoadSpriteMapFromFile() { if (loadSpriteMapDialog.ShowDialog() == DialogResult.OK) { Dictionary loadedSpriteMap; using (XmlReader xmlRead = XmlReader.Create(loadSpriteMapDialog.FileName)) { loadedSpriteMap = IntermediateSerializer.Deserialize>(xmlRead, null); } spriteMap = new BindingList(); foreach (KeyValuePair sprite in loadedSpriteMap) { spriteMap.Add(sprite.Value); } spriteMapListBox.DataSource = spriteMap; spriteMapListBox.DisplayMember = "Name"; spriteMapListBox.ValueMember = "Bounds"; //enable spriteMap-required controls addSpriteToFrameListButton.Enabled = true; addSpriteToFrameSpritesButton.Enabled = true; replaceSpriteButton.Enabled = true; if (currentAnimation == null) { frames = new BindingList(); frameListBox.DataSource = frames; frameListBox.DisplayMember = "Name"; InitiateAnimationList(); } EnableAnimationEditingControls(); return loadedSpriteMap; } return null; } private void animationPreview_FrameChanged(object sender, EventArgs e) { frameTrackBar.Value = animationPreview.PreviewSprite.CurrentFrameIndex; } private void animationPreview_AnimationEnded(object sender, EventArgs e) { animationPreview.Playing = false; frameTrackBar.Enabled = true; frameListBox_SetSingleSelection(frameTrackBar.Value); } private void animationLoopCheckBox_CheckedChanged(object sender, EventArgs e) { currentAnimation.IsLooping = animationLoopCheckBox.Checked; } private void playAnimationButton_Click(object sender, EventArgs e) { if (currentAnimation.Frames.Count() > 0) { animationPreview.Playing = !animationPreview.Playing; if (!animationPreview.Playing) { frameTrackBar.Enabled = true; frameListBox_SetSingleSelection(frameTrackBar.Value); } else { animationPreview.PreviewSprite.CurrentAnimation = currentAnimation; frameTrackBar.Enabled = false; } } } private void frameTrackBar_Scroll(object sender, EventArgs e) { if (!animationPreview.Playing) { tracking = true; animationPreview.PreviewSprite.CurrentFrameIndex = frameTrackBar.Value; frameListBox_SetSingleSelection(frameTrackBar.Value); tracking = false; } } private void moveFrameDownButton_Click(object sender, EventArgs e) { if (frameListBox.SelectedIndices.Count == 1 && frameListBox.SelectedIndex < frames.Count - 1) { MoveFrame(frames, frameListBox, 1); } } private void moveFrameUpButton_Click(object sender, EventArgs e) { if (frameListBox.SelectedIndices.Count == 1 && frameListBox.SelectedIndex > 0) { MoveFrame(frames, frameListBox, -1); } } private void MoveFrame(BindingList frameList, ListBox frameBox, int dir) { DoModification(new ReorderFrame(frameList, frameBox, dir)); UpdateAnimation(); } private void removeFrameButton_Click(object sender, EventArgs e) { if(frameListBox.SelectedIndices.Count == 1 && frameListBox.SelectedIndex > -1 && frameListBox.SelectedIndex < frames.Count) { DoModification(new RemoveFrame(frames, frameListBox, frameListBox.SelectedIndex)); UpdateAnimation(); } } private void saveAnimationToolStripMenuItem_Click(object sender, EventArgs e) { if (saveAnimationDialog.ShowDialog() == DialogResult.OK) { using (XmlWriter writer = XmlWriter.Create(saveAnimationDialog.FileName)) { IntermediateSerializer.Serialize(writer, currentAnimation, null); } } } private void loadAnimationToolStripMenuItem_Click(object sender, EventArgs e) { if (loadAnimationDialog.ShowDialog() == DialogResult.OK) { Animation loadedAnimation; using (XmlReader xmlRead = XmlReader.Create(loadAnimationDialog.FileName)) { loadedAnimation = IntermediateSerializer.Deserialize(xmlRead, null); } if (animationBox.Enabled == false) { EnableAnimationEditingControls(); InitiateAnimationList(); } AddAnimation(loadedAnimation); currentAnimation = animations[animationBox.SelectedIndex]; ReadAnimationInfo(); UpdateAnimation(); } } private void AddAnimation(Animation anim) { DoModification(new AddAnimation(animations, animationBox, anim)); } private void animationBox_SelectedIndexChanged(object sender, EventArgs e) { currentAnimation = animations[animationBox.SelectedIndex]; ReadAnimationInfo(); UpdateAnimation(); } private void addAnimationButton_Click(object sender, EventArgs e) { AddAnimation(new Animation()); } private void removeAnimationButton_Click(object sender, EventArgs e) { if (animationBox.SelectedIndex > -1 && animationBox.SelectedIndex < animations.Count) { DoModification(new RemoveAnimation(animations, animationBox, animationBox.SelectedIndex)); ReadAnimationInfo(); UpdateAnimation(); } } private void importDelayBox_ValueChanged(object sender, EventArgs e) { importDelay = (float)importDelayBox.Value / frameRate; } private void animationPreview_MouseWheel(object sender, MouseEventArgs e) { if(Math.Sign(e.Delta) > 0) { animationPreview.ZoomLevel++; } else if (animationPreview.ZoomLevel > 1) { animationPreview.ZoomLevel--; } } private void saveAnimationSetToolStripMenuItem_Click(object sender, EventArgs e) { var animSet = new AnimationSet(); foreach(Animation anim in animations) { if (animSet.Anims.ContainsKey(anim.Name)) { string errorMessage = "Duplicate names found! Please ensure all animations have unique names."; string caption = "Save Aborted"; MessageBoxButtons saveError = MessageBoxButtons.OK; MessageBox.Show(errorMessage, caption, saveError); return; } else { animSet.Add(anim); } } if (saveAnimationSetDialog.ShowDialog() == DialogResult.OK) { using (XmlWriter writer = XmlWriter.Create(saveAnimationSetDialog.FileName)) { IntermediateSerializer.Serialize(writer, animSet, null); } } } private void loadAnimationSetToolStripMenuItem_Click(object sender, EventArgs e) { if (loadAnimationSetDialog.ShowDialog() == DialogResult.OK) { undoHistory = new History(); AnimationSet loadedAnimSet; using (XmlReader xmlRead = XmlReader.Create(loadAnimationSetDialog.FileName)) { loadedAnimSet = IntermediateSerializer.Deserialize(xmlRead, null); } if (animationBox.Enabled == false) { EnableAnimationEditingControls(); } InitiateAnimationList(newAnimation: false); foreach(KeyValuePair currentEntry in loadedAnimSet.Anims) { animations.Add(currentEntry.Value); } animationBox.SelectedIndex = 0; currentAnimation = animations[animationBox.SelectedIndex]; ReadAnimationInfo(); UpdateAnimation(); } } private void updateAnimationSpritesFromSpriteMapToolStripMenuItem_Click(object sender, EventArgs e) { var loadedSpriteMap = LoadSpriteMapFromFile(); using(XmlReader xmlRead = XmlReader.Create(loadSpriteMapDialog.FileName)) { loadedSpriteMap = IntermediateSerializer.Deserialize>(xmlRead, null); } int updateCounter = 0; foreach(Animation anim in animations) { foreach(Frame frame in anim.Frames) { if (frame.Sprites != null) { for (int index = 0; index < frame.Sprites.Count; index++) { var sprite = frame.Sprites[index]; if (loadedSpriteMap.ContainsKey(sprite.Name)) { frame.Sprites[index] = loadedSpriteMap[sprite.Name]; updateCounter++; } } } } } MessageBox.Show("Updated " + updateCounter + " sprites!"); } private void frameSpriteListBox_SelectedIndexChanged(object sender, EventArgs e) { if(!tracking) UpdateEnabledControls(); if (frameSpriteListBox.SelectedIndex > -1 && frameSpriteListBox.SelectedIndex < frameSprites.Count) { //check if animationPreview has initialized EditSprites; do so if not, and if so, clear it if (animationPreview.EditSprites == null) animationPreview.EditSprites = new List(); else animationPreview.EditSprites.Clear(); //add selected sprites to EditSprites list foreach(int index in frameSpriteListBox.SelectedIndices) { if(index < frameSprites.Count) animationPreview.EditSprites.Add(frameSprites[index]); } if (frameSpriteListBox.SelectedIndices.Count == 1) { ReadSpriteOffset(); } } } //load selected sprite's position into the sprite position boxes private void ReadSpriteOffset() { reading = true; spriteXPosBox.Value = (int)frameSprites[frameSpriteListBox.SelectedIndex].Offset.X; spriteYPosBox.Value = (int)frameSprites[frameSpriteListBox.SelectedIndex].Offset.Y; reading = false; } //update X offset of selected sprite with position entered into spriteXPosBox private void spriteXPosBox_ValueChanged(object sender, EventArgs e) { if (reading || frameSpriteListBox.SelectedIndex >= currentFrame.Sprites.Count) return; SetSpritePosition(); currentFrame.Sprites = frameSprites.ToList(); /* var newPos = currentFrame.Sprites[frameSpriteListBox.SelectedIndex].Offset; newPos.X = (float)spriteXPosBox.Value; currentFrame.Sprites[frameSpriteListBox.SelectedIndex].Offset = newPos; */ } //update Y offset of selected sprite with position entered into spriteYPosBox private void spriteYPosBox_ValueChanged(object sender, EventArgs e) { if (reading || frameSpriteListBox.SelectedIndex >= currentFrame.Sprites.Count) return; SetSpritePosition(); currentFrame.Sprites = frameSprites.ToList(); /* var newPos = currentFrame.Sprites[frameSpriteListBox.SelectedIndex].Offset; newPos.Y = (float)spriteYPosBox.Value; currentFrame.Sprites[frameSpriteListBox.SelectedIndex].Offset = newPos; */ } private void SetSpritePosition() { DoModification(new SetSpritePosition(frameSprites, frameSpriteListBox, new Vector2((float)spriteXPosBox.Value, (float)spriteYPosBox.Value))); } private void addSpriteToFrameSpritesButton_Click(object sender, EventArgs e) { var spriteToAdd = spriteMap[spriteMapListBox.SelectedIndex].Clone(); DoModification(new AddSprite(frameSprites, frameSpriteListBox, currentFrame, spriteToAdd)); } private void addEmptyFrameButton_Click(object sender, EventArgs e) { AddFrame(new Frame()); } private void RenameFrames() { bool changed = false; foreach(int index in frameListBox.SelectedIndices) { if (frames[index].Name != frameNameBox.Text) { changed = true; break; } } if (changed) { DoModification(new RenameFrame(frames, frameListBox, frameNameBox.Text)); } } private void moveSpriteDownButton_Click(object sender, EventArgs e) { if (frameSpriteListBox.SelectedIndex < currentFrame.Sprites.Count - 1) { MoveSprite(frameSprites, frameSpriteListBox, 1); } } private void moveSpriteUpButton_Click(object sender, EventArgs e) { if (frameSpriteListBox.SelectedIndex > 0) { MoveSprite(frameSprites, frameSpriteListBox, -1); } } private void MoveSprite(BindingList spriteList, ListBox spriteBox, int dir) { DoModification(new ReorderSprite(spriteList, spriteBox, dir)); UpdateAnimation(); } private void removeSpriteButton_Click(object sender, EventArgs e) { if (frameSpriteListBox.SelectedIndex > -1 && frameSpriteListBox.SelectedIndex < frameSprites.Count) { DoModification(new RemoveSprite(frameSprites, frameSpriteListBox, frameSpriteListBox.SelectedIndex)); UpdateAnimation(); } } private void animationPreview_SpriteDragUpdate(object sender, EventArgs e) { if(frameSpriteListBox.SelectedIndices.Count == 1) ReadSpriteOffset(); } private void duplicateFrameButton_Click(object sender, EventArgs e) { AddFrame(currentFrame.Clone()); } private void replaceSpriteButton_Click(object sender, EventArgs e) { DoModification(new ReplaceSprite(frameSprites, frameSpriteListBox.SelectedIndex, spriteMap[spriteMapListBox.SelectedIndex])); UpdateAnimation(); } private void undoToolStripMenuItem_Click(object sender, EventArgs e) { undoHistory.Undo(animationBox, frameListBox, frameSpriteListBox); UpdateAnimation(); } private void redoToolStripMenuItem_Click(object sender, EventArgs e) { undoHistory.Redo(animationBox, frameListBox, frameSpriteListBox); UpdateAnimation(); } private void UpdateAnimationName() { DoModification(new RenameAnimation(animations, animationBox.SelectedIndex, animationNameBox.Text)); } private void animationNameBox_Leave(object sender, EventArgs e) { if (animations[animationBox.SelectedIndex].Name != animationNameBox.Text) { UpdateAnimationName(); } } private void animationNameBox_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == (char)Keys.Return && animations[animationBox.SelectedIndex].Name != animationNameBox.Text) { UpdateAnimationName(); } } private void frameNameBox_Leave(object sender, EventArgs e) { if (frameNameBoxEdited) RenameFrames(); frameNameBoxEdited = false; } private void frameNameBox_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == (char)Keys.Return && frameNameBoxEdited) RenameFrames(); frameNameBoxEdited = false; } private void frameNameBox_TextChanged(object sender, EventArgs e) { if(!reading && !tracking) { frameNameBoxEdited = true; } } private void frameListBox_DrawItem(object sender, DrawItemEventArgs e) { // Draw the background of the ListBox control for each item. //e.DrawBackground(); // Define the default color of the brush as black. Brush nameBrush = Brushes.Black; Brush indexBrush = Brushes.Gray; if (e.BackColor.GetBrightness() < 0.5) nameBrush = Brushes.White; System.Drawing.Rectangle indexRect = e.Bounds; System.Drawing.Rectangle nameRect = e.Bounds; indexRect.Width = 26; nameRect.X += 24; nameRect.Width -= 24; string indexString = ""; for(int index = 3 - e.Index.ToString().Length; index > 0; index--) { indexString += "0"; } indexString += e.Index; indexString += ":"; e.Graphics.FillRectangle(Brushes.White, indexRect); e.Graphics.FillRectangle(new SolidBrush(e.BackColor), nameRect); // Draw the current item text based on the current Font // and the custom brush settings. e.Graphics.DrawString(indexString, e.Font, indexBrush, indexRect, StringFormat.GenericDefault); e.Graphics.DrawString(frames[e.Index].Name, e.Font, nameBrush, nameRect, StringFormat.GenericDefault); // If the ListBox has focus, draw a focus rectangle around the selected item. e.DrawFocusRectangle(); } } }