using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Windows.Forms; using BizHawk.Client.Common; using BizHawk.Client.EmuHawk.WinFormExtensions; namespace BizHawk.Client.EmuHawk { public partial class BookmarksBranchesBox : UserControl { private const string BranchNumberColumnName = "BranchNumberColumn"; private const string FrameColumnName = "FrameColumn"; private const string UserTextColumnName = "TextColumn"; private readonly ScreenshotForm Screenshot = new ScreenshotForm(); private TasMovie Movie => Tastudio.CurrentTasMovie; private TasBranch _backupBranch; private BranchUndo _branchUndo = BranchUndo.None; private int LongestBranchText = 0; private enum BranchUndo { Load, Update, Text, Remove, None } public Action LoadedCallback { get; set; } private void CallLoadedCallback(int index) { LoadedCallback?.Invoke(index); } public Action SavedCallback { get; set; } private void CallSavedCallback(int index) { SavedCallback?.Invoke(index); } public Action RemovedCallback { get; set; } private void CallRemovedCallback(int index) { RemovedCallback?.Invoke(index); } public TAStudio Tastudio { get; set; } public int HoverInterval { get { return BranchView.HoverInterval; } set { BranchView.HoverInterval = value; } } public BookmarksBranchesBox() { InitializeComponent(); BranchView.AllColumns.AddRange(new[] { new InputRoll.RollColumn { Name = BranchNumberColumnName, Text = "#", Width = 30 }, new InputRoll.RollColumn { Name = FrameColumnName, Text = "Frame", Width = 64 }, new InputRoll.RollColumn { Name = UserTextColumnName, Text = "UserText", Width = 90 }, }); BranchView.QueryItemText += QueryItemText; BranchView.QueryItemBkColor += QueryItemBkColor; } #region Query callbacks private void QueryItemText(int index, InputRoll.RollColumn column, out string text, ref int offsetX, ref int offsetY) { text = ""; if (index >= Movie.BranchCount) { return; } switch (column.Name) { case BranchNumberColumnName: text = index.ToString(); break; case FrameColumnName: text = GetBranch(index).Frame.ToString(); break; case UserTextColumnName: text = GetBranch(index).UserText; break; } } private void QueryItemBkColor(int index, InputRoll.RollColumn column, ref Color color) { TasBranch branch = GetBranch(index); if (branch != null) { var record = Tastudio.CurrentTasMovie[branch.Frame]; if (index == Movie.CurrentBranch) { color = TAStudio.CurrentFrame_InputLog; } else if (record.Lagged.HasValue) { color = record.Lagged.Value ? TAStudio.LagZone_InputLog : TAStudio.GreenZone_InputLog; } } // Highlight the branch cell a little, if hovering over it if (BranchView.CurrentCellIsDataCell && BranchView.CurrentCell.Column.Name == BranchNumberColumnName && column.Name == BranchNumberColumnName && index == BranchView.CurrentCell.RowIndex) { color = Color.FromArgb((byte)(color.A - 24), (byte)(color.R - 24), (byte)(color.G - 24), (byte)(color.B - 24)); } } #endregion #region Actions private TasBranch GetBranch(int index) { return Tastudio.CurrentTasMovie.GetBranch(index); } public void Branch() { TasBranch branch = CreateBranch(); Movie.NewBranchText = ""; // reset every time it's used Movie.AddBranch(branch); BranchView.RowCount = Movie.BranchCount; Movie.CurrentBranch = Movie.BranchCount - 1; BranchView.ScrollToIndex(Movie.CurrentBranch); BranchView.Refresh(); Tastudio.RefreshDialog(); } public TasBranch SelectedBranch { get { if (BranchView.AnyRowsSelected) { return GetBranch(BranchView.SelectedRows.First()); } return null; } } private TasBranch CreateBranch() { return new TasBranch { Frame = Tastudio.Emulator.Frame, CoreData = (byte[])(Tastudio.StatableEmulator.SaveStateBinary().Clone()), InputLog = Movie.InputLog.Clone(), OSDFrameBuffer = GlobalWin.MainForm.CaptureOSD(), LagLog = Movie.TasLagLog.Clone(), ChangeLog = new TasMovieChangeLog(Movie), TimeStamp = DateTime.Now, Markers = Movie.Markers.DeepClone(), UserText = Movie.NewBranchText }; } private void LoadBranch(TasBranch branch) { Tastudio.CurrentTasMovie.LoadBranch(branch); var stateInfo = new KeyValuePair(branch.Frame, branch.CoreData); Tastudio.LoadState(stateInfo); QuickBmpFile.Copy(new BitmapBufferVideoProvider(branch.OSDFrameBuffer), Tastudio.VideoProvider); GlobalWin.MainForm.PauseOnFrame = null; Tastudio.RefreshDialog(); } private void UpdateBranch(TasBranch branch) { Movie.UpdateBranch(branch, CreateBranch()); Tastudio.RefreshDialog(); } private void LoadSelectedBranch() { if (SelectedBranch != null) { int index = BranchView.SelectedRows.First(); Movie.CurrentBranch = index; LoadBranch(SelectedBranch); BranchView.Refresh(); GlobalWin.OSD.AddMessage("Loaded branch " + Movie.CurrentBranch.ToString()); } } private void BranchesContextMenu_Opening(object sender, CancelEventArgs e) { UpdateBranchContextMenuItem.Enabled = RemoveBranchContextMenuItem.Enabled = LoadBranchContextMenuItem.Enabled = EditBranchTextContextMenuItem.Enabled = JumpToBranchContextMenuItem.Enabled = SelectedBranch != null; } private void AddBranchToolStripMenuItem_Click(object sender, EventArgs e) { Branch(); CallSavedCallback(Tastudio.CurrentTasMovie.BranchCount - 1); GlobalWin.OSD.AddMessage("Added branch " + Movie.CurrentBranch.ToString()); } private void AddBranchWithTexToolStripMenuItem_Click(object sender, EventArgs e) { Branch(); EditBranchTextPopUp(Movie.CurrentBranch); CallSavedCallback(Tastudio.CurrentTasMovie.BranchCount - 1); GlobalWin.OSD.AddMessage("Added branch " + Movie.CurrentBranch.ToString()); } private void LoadBranchToolStripMenuItem_Click(object sender, EventArgs e) { _backupBranch = CreateBranch(); var currentHashes = Movie.Branches.Select(b => b.UniqueIdentifier.GetHashCode()).ToList(); do { _backupBranch.UniqueIdentifier = Guid.NewGuid(); } while (currentHashes.Contains(_backupBranch.UniqueIdentifier.GetHashCode())); UndoBranchToolStripMenuItem.Enabled = UndoBranchButton.Enabled = true; UndoBranchToolStripMenuItem.Text = "Undo Branch Load"; toolTip1.SetToolTip(UndoBranchButton, "Undo Branch Load"); _branchUndo = BranchUndo.Load; LoadSelectedBranch(); CallLoadedCallback(BranchView.SelectedRows.First()); } private void UpdateBranchToolStripMenuItem_Click(object sender, EventArgs e) { if (SelectedBranch != null) { Movie.CurrentBranch = BranchView.SelectedRows.First(); _backupBranch = SelectedBranch.Clone(); UndoBranchToolStripMenuItem.Enabled = UndoBranchButton.Enabled = true; UndoBranchToolStripMenuItem.Text = "Undo Branch Update"; toolTip1.SetToolTip(UndoBranchButton, "Undo Branch Update"); _branchUndo = BranchUndo.Update; UpdateBranch(SelectedBranch); CallSavedCallback(Movie.CurrentBranch); GlobalWin.OSD.AddMessage("Saved branch " + Movie.CurrentBranch); } } private void EditBranchTextToolStripMenuItem_Click(object sender, EventArgs e) { if (SelectedBranch != null) { int index = BranchView.SelectedRows.First(); string oldText = SelectedBranch.UserText; if (EditBranchTextPopUp(index)) { _backupBranch = SelectedBranch.Clone(); _backupBranch.UserText = oldText; UndoBranchToolStripMenuItem.Enabled = UndoBranchButton.Enabled = true; UndoBranchToolStripMenuItem.Text = "Undo Branch Text Edit"; toolTip1.SetToolTip(UndoBranchButton, "Undo Branch Text Edit"); _branchUndo = BranchUndo.Text; GlobalWin.OSD.AddMessage("Edited branch " + index.ToString()); } } } private void JumpToBranchToolStripMenuItem_Click(object sender, EventArgs e) { if (SelectedBranch != null) { int index = BranchView.SelectedRows.First(); TasBranch branch = Movie.GetBranch(index); Tastudio.GoToFrame(branch.Frame); } } private void RemoveBranchToolStripMenuItem_Click(object sender, EventArgs e) { if (SelectedBranch != null) { int index = BranchView.SelectedRows.First(); if (index == Movie.CurrentBranch) { Movie.CurrentBranch = -1; } else if (index < Movie.CurrentBranch) { Movie.CurrentBranch--; } _backupBranch = SelectedBranch.Clone(); UndoBranchToolStripMenuItem.Enabled = UndoBranchButton.Enabled = true; UndoBranchToolStripMenuItem.Text = "Undo Branch Removal"; toolTip1.SetToolTip(UndoBranchButton, "Undo Branch Removal"); _branchUndo = BranchUndo.Remove; Movie.RemoveBranch(SelectedBranch); BranchView.RowCount = Movie.BranchCount; if (index == Movie.BranchCount) { BranchView.ClearSelectedRows(); BranchView.SelectRow(Movie.BranchCount - 1, true); } CallRemovedCallback(index); Tastudio.RefreshDialog(); GlobalWin.OSD.AddMessage("Removed branch " + index.ToString()); } } private void UndoBranchToolStripMenuItem_Click(object sender, EventArgs e) { if (_branchUndo == BranchUndo.Load) { LoadBranch(_backupBranch); CallLoadedCallback(Tastudio.CurrentTasMovie.Branches.IndexOf(_backupBranch)); GlobalWin.OSD.AddMessage("Branch Load canceled"); } else if (_branchUndo == BranchUndo.Update) { Movie.UpdateBranch(Movie.GetBranch(_backupBranch.UniqueIdentifier), _backupBranch); CallSavedCallback(Tastudio.CurrentTasMovie.Branches.IndexOf(_backupBranch)); GlobalWin.OSD.AddMessage("Branch Update canceled"); } else if (_branchUndo == BranchUndo.Text) { Movie.GetBranch(_backupBranch.UniqueIdentifier).UserText = _backupBranch.UserText; GlobalWin.OSD.AddMessage("Branch Text Edit canceled"); } else if (_branchUndo == BranchUndo.Remove) { Movie.AddBranch(_backupBranch); BranchView.RowCount = Movie.BranchCount; CallSavedCallback(Tastudio.CurrentTasMovie.Branches.IndexOf(_backupBranch)); GlobalWin.OSD.AddMessage("Branch Removal canceled"); } UndoBranchToolStripMenuItem.Enabled = UndoBranchButton.Enabled = false; BranchView.Refresh(); Tastudio.RefreshDialog(); } public void AddBranchExternal() { AddBranchToolStripMenuItem_Click(null, null); BranchView.SelectRow(Movie.CurrentBranch, true); BranchView.Refresh(); } public void LoadBranchExternal(int slot = -1) { if (Tastudio.FloatEditingMode) { return; } if (slot != -1) { if (GetBranch(slot) != null) { BranchView.SelectRow(slot, true); } else { NonExistentBranchMessage(slot); return; } } LoadBranchToolStripMenuItem_Click(null, null); } public void UpdateBranchExternal(int slot = -1) { if (Tastudio.FloatEditingMode) { return; } if (slot != -1) { if (GetBranch(slot) != null) { BranchView.SelectRow(slot, true); } else { NonExistentBranchMessage(slot); return; } } UpdateBranchToolStripMenuItem_Click(null, null); } public void RemoveBranchExtrenal() { RemoveBranchToolStripMenuItem_Click(null, null); } public void SelectBranchExternal(int slot) { if (Tastudio.FloatEditingMode) { return; } if (GetBranch(slot) != null) { BranchView.SelectRow(slot, true); BranchView.Refresh(); } else { NonExistentBranchMessage(slot); } } public void SelectBranchExternal(bool next) { if (SelectedBranch == null) { BranchView.SelectRow(Movie.CurrentBranch, true); BranchView.Refresh(); return; } int sel = BranchView.SelectedRows.First(); if (next) { if (GetBranch(sel + 1) != null) { BranchView.SelectRow(sel, false); BranchView.SelectRow(sel + 1, true); } } else // previous { if (GetBranch(sel - 1) != null) { BranchView.SelectRow(sel, false); BranchView.SelectRow(sel - 1, true); } } BranchView.Refresh(); } public void NonExistentBranchMessage(int slot) { string binding = Global.Config.HotkeyBindings.First(x => x.DisplayName == "Add Branch").Bindings; GlobalWin.OSD.AddMessage($"Branch {slot} does not exist"); GlobalWin.OSD.AddMessage($"Use {binding} to add branches"); } public void UpdateValues() { BranchView.RowCount = Movie.BranchCount; BranchView.Refresh(); } public void Restart() { BranchView.DeselectAll(); BranchView.RowCount = Movie.BranchCount; BranchView.Refresh(); } public void UpdateTextColumnWidth() { int temp = 0; foreach (TasBranch b in Movie.Branches) { if (string.IsNullOrEmpty(b.UserText)) { continue; } if (temp < b.UserText.Length) { temp = b.UserText.Length; } } LongestBranchText = temp; int textWidth = (LongestBranchText * 12) + 14; // sorry for magic numbers. see TAStudio.SetUpColumns() var column = BranchView.AllColumns.Single(c => c.Name == UserTextColumnName); if (textWidth < 90) { textWidth = 90; } if (column.Width != textWidth) { column.Width = textWidth; BranchView.AllColumns.ColumnsChanged(); } } public bool EditBranchTextPopUp(int index) { TasBranch branch = Movie.GetBranch(index); if (branch == null) { return false; } var i = new InputPrompt { Text = "Text for branch " + index, TextInputType = InputPrompt.InputType.Text, Message = "Enter a message", InitialValue = branch.UserText }; var point = Cursor.Position; point.Offset(i.Width / -2, i.Height / -2); var result = i.ShowHawkDialog(position: point); if (result == DialogResult.OK) { branch.UserText = i.PromptText; UpdateTextColumnWidth(); UpdateValues(); return true; } return false; } #endregion #region Events private void BranchView_MouseDown(object sender, MouseEventArgs e) { UpdateBranchButton.Enabled = LoadBranchButton.Enabled = JumpToBranchButton.Enabled = SelectedBranch != null; BranchesContextMenu.Close(); if (e.Button == MouseButtons.Left) { if (BranchView.CurrentCell != null && BranchView.CurrentCell.IsDataCell && BranchView.CurrentCell.Column.Name == BranchNumberColumnName) { BranchView.DragCurrentCell(); } } } private void BranchView_MouseUp(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { BranchView.ReleaseCurrentCell(); } } private void BranchView_MouseDoubleClick(object sender, MouseEventArgs e) { LoadBranchToolStripMenuItem_Click(null, null); } private void BranchView_MouseMove(object sender, MouseEventArgs e) { if (BranchView.CurrentCell?.RowIndex == null || BranchView.CurrentCell.Column == null) { Screenshot.FadeOut(); } else if (BranchView.CurrentCell.Column.Name == BranchNumberColumnName) { BranchView.Refresh(); } } private void BranchView_MouseLeave(object sender, EventArgs e) { Screenshot.FadeOut(); } private void BranchView_CellDropped(object sender, InputRoll.CellEventArgs e) { if (e.NewCell != null && e.NewCell.IsDataCell && e.OldCell.RowIndex.Value < Movie.BranchCount) { Movie.SwapBranches(e.OldCell.RowIndex.Value, e.NewCell.RowIndex.Value); } } private void BranchView_PointedCellChanged(object sender, InputRoll.CellEventArgs e) { if (e.NewCell?.RowIndex != null && e.NewCell.Column != null && e.NewCell.RowIndex < Movie.BranchCount) { if (BranchView.CurrentCell.Column.Name == BranchNumberColumnName && BranchView.CurrentCell.RowIndex.HasValue && BranchView.CurrentCell.RowIndex < Movie.BranchCount) { TasBranch branch = GetBranch(BranchView.CurrentCell.RowIndex.Value); Point location = Location; int width = branch.OSDFrameBuffer.Width; int height = branch.OSDFrameBuffer.Height; location.Offset(-width, 0); Screenshot.UpdateValues(branch, PointToScreen(location), width, height, (int)Graphics.FromHwnd(this.Handle).MeasureString( branch.UserText, Screenshot.Font, width).Height); Screenshot.FadeIn(); } else { Screenshot.FadeOut(); } } else { Screenshot.FadeOut(); } } #endregion } }