1 using System; 2 using System.Drawing; 3 using System.Threading.Tasks; 4 using System.Windows.Forms; 5 using Mesen.GUI.Config; 6 using Mesen.GUI.Forms; 7 8 namespace Mesen.GUI.Debugger 9 { 10 public partial class frmPpuViewer : BaseForm 11 { 12 private DateTime _lastUpdate = DateTime.MinValue; 13 private InteropEmu.NotificationListener _notifListener; 14 private TabPage _selectedTab; 15 private bool _refreshing = false; 16 private Size _originalSize; 17 private bool _isCompact = false; 18 private bool _isZoomed = false; 19 private PpuViewerMode _mode; 20 21 private static int _nextPpuViewerId = 0; 22 private int _ppuViewerId = 0; 23 frmPpuViewer(PpuViewerMode mode = PpuViewerMode.Combined)24 public frmPpuViewer(PpuViewerMode mode = PpuViewerMode.Combined) 25 { 26 InitializeComponent(); 27 28 _ppuViewerId = GetNextPpuViewerId(); 29 _mode = mode; 30 31 if(Program.IsMono) { 32 btnToggleView.Top -= 1; 33 chkToggleZoom.Top -= 1; 34 } 35 36 this._selectedTab = this.tpgNametableViewer; 37 this.mnuAutoRefresh.Checked = ConfigManager.Config.DebugInfo.PpuAutoRefresh; 38 this.mnuRefreshOnBreak.Checked = ConfigManager.Config.DebugInfo.PpuRefreshOnBreak; 39 this.mnuShowInformationOverlay.Checked = ConfigManager.Config.DebugInfo.PpuShowInformationOverlay; 40 this.ctrlNametableViewer.Connect(this.ctrlChrViewer); 41 42 Point? startupLocation; 43 switch(_mode) { 44 case PpuViewerMode.NametableViewer: startupLocation = ConfigManager.Config.DebugInfo.PpuNametableViewerLocation; break; 45 case PpuViewerMode.ChrViewer: startupLocation = ConfigManager.Config.DebugInfo.PpuChrViewerLocation; break; 46 case PpuViewerMode.SpriteViewer: startupLocation = ConfigManager.Config.DebugInfo.PpuSpriteViewerLocation; break; 47 case PpuViewerMode.PaletteViewer: startupLocation = ConfigManager.Config.DebugInfo.PpuPaletteViewerLocation; break; 48 default: startupLocation = ConfigManager.Config.DebugInfo.PpuWindowLocation; break; 49 } 50 51 if(startupLocation.HasValue) { 52 this.StartPosition = FormStartPosition.Manual; 53 this.Location = startupLocation.Value; 54 } 55 } 56 GetNextPpuViewerId()57 public static int GetNextPpuViewerId() 58 { 59 return _nextPpuViewerId++; 60 } 61 InitShortcuts()62 private void InitShortcuts() 63 { 64 mnuRefresh.InitShortcut(this, nameof(DebuggerShortcutsConfig.Refresh)); 65 } 66 OnLoad(EventArgs e)67 protected override void OnLoad(EventArgs e) 68 { 69 base.OnLoad(e); 70 71 if(!this.DesignMode) { 72 this._notifListener = new InteropEmu.NotificationListener(ConfigManager.Config.DebugInfo.DebugConsoleId); 73 this._notifListener.OnNotification += this._notifListener_OnNotification; 74 75 this.ctrlScanlineCycle.Initialize(_ppuViewerId, ConfigManager.Config.DebugInfo.PpuDisplayScanline, ConfigManager.Config.DebugInfo.PpuDisplayCycle); 76 77 GetData(); 78 79 this.ctrlNametableViewer.RefreshViewer(); 80 this.ctrlChrViewer.RefreshViewer(); 81 this.ctrlSpriteViewer.RefreshViewer(); 82 this.ctrlPaletteViewer.RefreshViewer(); 83 84 this.InitShortcuts(); 85 this.UpdateRefreshSpeedMenu(); 86 87 string toggleViewTooltip = "Toggle Compact/Normal View"; 88 if(ConfigManager.Config.DebugInfo.Shortcuts.PpuViewer_ToggleView != Keys.None) { 89 toggleViewTooltip += " (" + DebuggerShortcutsConfig.GetShortcutDisplay(ConfigManager.Config.DebugInfo.Shortcuts.PpuViewer_ToggleView) + ")"; 90 } 91 this.toolTip.SetToolTip(this.btnToggleView, toggleViewTooltip); 92 93 string toggleZoomTooltip = "Toggle 2x Zoom"; 94 if(ConfigManager.Config.DebugInfo.Shortcuts.PpuViewer_ToggleZoom != Keys.None) { 95 toggleZoomTooltip += " (" + DebuggerShortcutsConfig.GetShortcutDisplay(ConfigManager.Config.DebugInfo.Shortcuts.PpuViewer_ToggleZoom) + ")"; 96 } 97 this.toolTip.SetToolTip(this.chkToggleZoom, toggleZoomTooltip); 98 99 _selectedTab = tabMain.SelectedTab; 100 if(_mode != PpuViewerMode.Combined) { 101 TabPage page = _selectedTab; 102 switch(_mode) { 103 case PpuViewerMode.NametableViewer: page = tpgNametableViewer; break; 104 case PpuViewerMode.ChrViewer: page = tpgChrViewer; break; 105 case PpuViewerMode.SpriteViewer: page = tpgSpriteViewer; break; 106 case PpuViewerMode.PaletteViewer: page = tpgPaletteViewer; break; 107 } 108 109 _selectedTab = page; 110 tabMain.SelectTab(page); 111 ToggleView(); 112 } 113 } 114 } 115 OnFormClosing(FormClosingEventArgs e)116 protected override void OnFormClosing(FormClosingEventArgs e) 117 { 118 base.OnFormClosing(e); 119 this._notifListener.OnNotification -= this._notifListener_OnNotification; 120 121 Point location = this.WindowState != FormWindowState.Normal ? this.RestoreBounds.Location : this.Location; 122 switch(_mode) { 123 case PpuViewerMode.NametableViewer: ConfigManager.Config.DebugInfo.PpuNametableViewerLocation = location; break; 124 case PpuViewerMode.ChrViewer: ConfigManager.Config.DebugInfo.PpuChrViewerLocation = location; break; 125 case PpuViewerMode.SpriteViewer: ConfigManager.Config.DebugInfo.PpuSpriteViewerLocation = location; break; 126 case PpuViewerMode.PaletteViewer: ConfigManager.Config.DebugInfo.PpuPaletteViewerLocation = location; break; 127 default: ConfigManager.Config.DebugInfo.PpuWindowLocation = location; break; 128 } 129 130 ConfigManager.Config.DebugInfo.PpuDisplayScanline = ctrlScanlineCycle.Scanline; 131 ConfigManager.Config.DebugInfo.PpuDisplayCycle = ctrlScanlineCycle.Cycle; 132 ConfigManager.ApplyChanges(); 133 InteropEmu.DebugClearPpuViewerSettings(_ppuViewerId); 134 } 135 _notifListener_OnNotification(InteropEmu.NotificationEventArgs e)136 private void _notifListener_OnNotification(InteropEmu.NotificationEventArgs e) 137 { 138 switch(e.NotificationType) { 139 case InteropEmu.ConsoleNotificationType.CodeBreak: 140 case InteropEmu.ConsoleNotificationType.GamePaused: 141 if(ConfigManager.Config.DebugInfo.PpuRefreshOnBreak) { 142 this.GetData(); 143 this.BeginInvoke((MethodInvoker)(() => this.RefreshViewers())); 144 } 145 break; 146 147 case InteropEmu.ConsoleNotificationType.PpuViewerDisplayFrame: 148 if(e.Parameter.ToInt32() == _ppuViewerId) { 149 int refreshDelay = 60; 150 switch(ConfigManager.Config.DebugInfo.PpuAutoRefreshSpeed) { 151 case RefreshSpeed.Low: refreshDelay= 60; break; 152 case RefreshSpeed.Normal: refreshDelay = 30; break; 153 case RefreshSpeed.High: refreshDelay = 12; break; 154 } 155 if(ConfigManager.Config.DebugInfo.PpuAutoRefresh && !_refreshing && (DateTime.Now - _lastUpdate).Milliseconds > refreshDelay) { 156 _lastUpdate = DateTime.Now; 157 this.GetData(); 158 this.BeginInvoke((MethodInvoker)(() => this.RefreshViewers())); 159 } 160 } 161 break; 162 163 case InteropEmu.ConsoleNotificationType.GameLoaded: 164 //Configuration is lost when debugger is restarted (when switching game or power cycling) 165 ctrlScanlineCycle.RefreshSettings(); 166 break; 167 } 168 } 169 GetData()170 private void GetData() 171 { 172 if(_isCompact) { 173 //In compact mode, only load data for the current tab 174 if(_selectedTab == this.tpgNametableViewer) { 175 this.ctrlNametableViewer.GetData(); 176 } else if(_selectedTab == this.tpgChrViewer) { 177 this.ctrlChrViewer.GetData(); 178 } else if(_selectedTab == this.tpgSpriteViewer) { 179 this.ctrlSpriteViewer.GetData(); 180 } else if(_selectedTab == this.tpgPaletteViewer) { 181 this.ctrlPaletteViewer.GetData(); 182 } 183 } else { 184 this.ctrlNametableViewer.GetData(); 185 this.ctrlChrViewer.GetData(); 186 this.ctrlSpriteViewer.GetData(); 187 this.ctrlPaletteViewer.GetData(); 188 } 189 } 190 RefreshViewers()191 private void RefreshViewers() 192 { 193 _refreshing = true; 194 if(_selectedTab == this.tpgNametableViewer) { 195 this.ctrlNametableViewer.RefreshViewer(); 196 } else if(_selectedTab == this.tpgChrViewer) { 197 this.ctrlChrViewer.RefreshViewer(); 198 } else if(_selectedTab == this.tpgSpriteViewer) { 199 this.ctrlSpriteViewer.RefreshViewer(); 200 } else if(_selectedTab == this.tpgPaletteViewer) { 201 this.ctrlPaletteViewer.RefreshViewer(); 202 } 203 _refreshing = false; 204 } 205 mnuRefresh_Click(object sender, EventArgs e)206 private void mnuRefresh_Click(object sender, EventArgs e) 207 { 208 this.GetData(); 209 this.RefreshViewers(); 210 } 211 mnuClose_Click(object sender, EventArgs e)212 private void mnuClose_Click(object sender, EventArgs e) 213 { 214 this.Close(); 215 } 216 mnuAutoRefresh_Click(object sender, EventArgs e)217 private void mnuAutoRefresh_Click(object sender, EventArgs e) 218 { 219 ConfigManager.Config.DebugInfo.PpuAutoRefresh = this.mnuAutoRefresh.Checked; 220 ConfigManager.ApplyChanges(); 221 } 222 mnuRefreshOnBreak_Click(object sender, EventArgs e)223 private void mnuRefreshOnBreak_Click(object sender, EventArgs e) 224 { 225 ConfigManager.Config.DebugInfo.PpuRefreshOnBreak = this.mnuRefreshOnBreak.Checked; 226 ConfigManager.ApplyChanges(); 227 } 228 mnuShowInformationOverlay_Click(object sender, EventArgs e)229 private void mnuShowInformationOverlay_Click(object sender, EventArgs e) 230 { 231 ConfigManager.Config.DebugInfo.PpuShowInformationOverlay = this.mnuShowInformationOverlay.Checked; 232 ConfigManager.ApplyChanges(); 233 } 234 tabMain_SelectedIndexChanged(object sender, EventArgs e)235 private void tabMain_SelectedIndexChanged(object sender, EventArgs e) 236 { 237 bool wasZoomedIn = _isZoomed; 238 if(wasZoomedIn) { 239 this.ToggleZoom(); 240 } 241 242 this._selectedTab = this.tabMain.SelectedTab; 243 244 if(wasZoomedIn) { 245 this.ToggleZoom(); 246 } 247 248 if(InteropEmu.DebugIsExecutionStopped()) { 249 //Refresh data when changing tabs when not running 250 this.RefreshViewers(); 251 } 252 } 253 ToggleZoom()254 private void ToggleZoom() 255 { 256 ICompactControl ctrl = null; 257 if(_selectedTab == tpgChrViewer) { 258 ctrl = ctrlChrViewer; 259 } else if(_selectedTab == tpgPaletteViewer) { 260 ctrl = ctrlPaletteViewer; 261 } else if(_selectedTab == tpgSpriteViewer) { 262 ctrl = ctrlSpriteViewer; 263 } else if(_selectedTab == tpgNametableViewer) { 264 ctrl = ctrlNametableViewer; 265 } 266 267 if(!_isZoomed) { 268 Size pictureSize = ctrl.GetCompactSize(false); 269 this.Size += pictureSize; 270 _originalSize += pictureSize; 271 ctrl.ScaleImage(2); 272 _isZoomed = true; 273 } else { 274 Size pictureSize = ctrl.GetCompactSize(false); 275 Size halfSize = new Size(pictureSize.Width / 2, pictureSize.Height / 2); 276 this.Size -= halfSize; 277 _originalSize -= halfSize; 278 ctrl.ScaleImage(0.5); 279 _isZoomed = false; 280 } 281 chkToggleZoom.Checked = _isZoomed; 282 } 283 ProcessCmdKey(ref Message msg, Keys keyData)284 protected override bool ProcessCmdKey(ref Message msg, Keys keyData) 285 { 286 if(keyData == ConfigManager.Config.DebugInfo.Shortcuts.PpuViewer_ToggleZoom) { 287 ToggleZoom(); 288 return true; 289 } else if(keyData == ConfigManager.Config.DebugInfo.Shortcuts.PpuViewer_ToggleView) { 290 ToggleView(); 291 return true; 292 } 293 294 if(!this.ctrlScanlineCycle.ContainsFocus) { 295 if(this.tabMain.SelectedTab == tpgChrViewer) { 296 bool shift = keyData.HasFlag(Keys.Shift); 297 keyData &= ~Keys.Shift; 298 299 if(keyData >= Keys.D1 && keyData <= Keys.D9) { 300 if(shift) { 301 this.ctrlChrViewer.SelectPalette(keyData - Keys.D1); 302 } else { 303 this.ctrlChrViewer.SelectColor((keyData - Keys.D1) % 4); 304 } 305 return true; 306 } 307 if(keyData >= Keys.NumPad1 && keyData <= Keys.NumPad9) { 308 if(shift) { 309 this.ctrlChrViewer.SelectPalette(keyData - Keys.NumPad1); 310 } else { 311 this.ctrlChrViewer.SelectColor((keyData - Keys.NumPad1) % 4); 312 } 313 return true; 314 } 315 } 316 } 317 return base.ProcessCmdKey(ref msg, keyData); 318 } 319 SelectChrTile(int tileIndex, int paletteIndex, bool allowOpenWindow)320 public void SelectChrTile(int tileIndex, int paletteIndex, bool allowOpenWindow) 321 { 322 if(_isCompact && allowOpenWindow) { 323 //If in compact mode, don't move to the CHR tab, open or use another window instead 324 frmPpuViewer otherPpuViewer = null; 325 foreach(BaseForm frm in DebugWindowManager.GetWindows()) { 326 if(frm != this && frm is frmPpuViewer && (!((frmPpuViewer)frm)._isCompact || ((frmPpuViewer)frm)._selectedTab == ((frmPpuViewer)frm).tpgChrViewer)) { 327 //If a window exists and is either not in compact mode, or in compact mode and showing the CHR viewer, use it 328 otherPpuViewer = frm as frmPpuViewer; 329 break; 330 } 331 } 332 if(otherPpuViewer == null) { 333 //Open up a new viewer, in compact mode 334 otherPpuViewer = DebugWindowManager.OpenPpuViewer(PpuViewerMode.ChrViewer); 335 otherPpuViewer.SelectChrTile(tileIndex, paletteIndex, false); 336 } else { 337 //Reuse an existing viewer that's not in compact mode 338 otherPpuViewer.SelectChrTile(tileIndex, paletteIndex, false); 339 otherPpuViewer.BringToFront(); 340 } 341 } else { 342 if(!InteropEmu.DebugIsExecutionStopped() || ConfigManager.Config.DebugInfo.PpuRefreshOnBreak) { 343 //Only change the palette if execution is not stopped (or if we're configured to refresh the viewer on break/pause) 344 //Otherwise, the CHR viewer will refresh its data (and it might not match the data we loaded at the specified scanline/cycle anymore) 345 ctrlChrViewer.SelectedPaletteIndex = paletteIndex; 346 } 347 ctrlChrViewer.SelectedTileIndex = tileIndex; 348 tabMain.SelectTab(tpgChrViewer); 349 _selectedTab = tpgChrViewer; 350 } 351 } 352 ctrlNametableViewer_OnSelectChrTile(int tileIndex, int paletteIndex)353 private void ctrlNametableViewer_OnSelectChrTile(int tileIndex, int paletteIndex) 354 { 355 SelectChrTile(tileIndex, paletteIndex, true); 356 } 357 ctrlSpriteViewer_OnSelectTilePalette(int tileIndex, int paletteIndex)358 private void ctrlSpriteViewer_OnSelectTilePalette(int tileIndex, int paletteIndex) 359 { 360 SelectChrTile(tileIndex, paletteIndex, true); 361 } 362 UpdateRefreshSpeedMenu()363 private void UpdateRefreshSpeedMenu() 364 { 365 mnuAutoRefreshLow.Checked = ConfigManager.Config.DebugInfo.PpuAutoRefreshSpeed == RefreshSpeed.Low; 366 mnuAutoRefreshNormal.Checked = ConfigManager.Config.DebugInfo.PpuAutoRefreshSpeed == RefreshSpeed.Normal; 367 mnuAutoRefreshHigh.Checked = ConfigManager.Config.DebugInfo.PpuAutoRefreshSpeed == RefreshSpeed.High; 368 } 369 mnuAutoRefreshSpeed_Click(object sender, EventArgs e)370 private void mnuAutoRefreshSpeed_Click(object sender, EventArgs e) 371 { 372 if(sender == mnuAutoRefreshLow) { 373 ConfigManager.Config.DebugInfo.PpuAutoRefreshSpeed = RefreshSpeed.Low; 374 } else if(sender == mnuAutoRefreshNormal) { 375 ConfigManager.Config.DebugInfo.PpuAutoRefreshSpeed = RefreshSpeed.Normal; 376 } else if(sender == mnuAutoRefreshHigh) { 377 ConfigManager.Config.DebugInfo.PpuAutoRefreshSpeed = RefreshSpeed.High; 378 } 379 ConfigManager.ApplyChanges(); 380 381 UpdateRefreshSpeedMenu(); 382 } 383 ToggleCompactMode(PpuViewerMode mode, ICompactControl control, TabPage tab, string title)384 private void ToggleCompactMode(PpuViewerMode mode, ICompactControl control, TabPage tab, string title) 385 { 386 if(!_isCompact) { 387 Point tabTopLeft = tabMain.PointToScreen(Point.Empty); 388 Point tabContentTopLeft = tab.PointToScreen(Point.Empty); 389 390 int heightGap = tabContentTopLeft.Y - tabTopLeft.Y + ctrlScanlineCycle.Height; 391 392 _isCompact = true; 393 _originalSize = this.Size; 394 Size size = control.GetCompactSize(true); 395 int widthDiff = ((Control)control).Width - size.Width; 396 int heightDiff = ((Control)control).Height - size.Height; 397 398 this.Controls.Add((Control)control); 399 ((Control)control).BringToFront(); 400 401 tabMain.Visible = false; 402 ctrlScanlineCycle.Visible = false; 403 this.Text = title; 404 405 this.Size = new Size(this.Width - widthDiff, this.Height - heightDiff - heightGap + 3); 406 } else { 407 _mode = PpuViewerMode.Combined; 408 _isCompact = false; 409 this.Size = _originalSize; 410 tabMain.Visible = true; 411 tab.Controls.Add((Control)control); 412 ctrlScanlineCycle.Visible = true; 413 this.Text = "PPU Viewer"; 414 } 415 416 btnToggleView.Image = _isCompact ? Properties.Resources.Expand : Properties.Resources.Collapse; 417 } 418 ToggleView()419 private void ToggleView() 420 { 421 if(_selectedTab == tpgChrViewer) { 422 ToggleCompactMode(PpuViewerMode.ChrViewer, ctrlChrViewer, tpgChrViewer, "CHR Viewer"); 423 } else if(_selectedTab == tpgPaletteViewer) { 424 ToggleCompactMode(PpuViewerMode.PaletteViewer, ctrlPaletteViewer, tpgPaletteViewer, "Palette Viewer"); 425 } else if(_selectedTab == tpgSpriteViewer) { 426 ToggleCompactMode(PpuViewerMode.SpriteViewer, ctrlSpriteViewer, tpgSpriteViewer, "Sprite Viewer"); 427 } else if(_selectedTab == tpgNametableViewer) { 428 ToggleCompactMode(PpuViewerMode.NametableViewer, ctrlNametableViewer, tpgNametableViewer, "Nametable Viewer"); 429 } 430 } 431 btnToggleView_Click(object sender, EventArgs e)432 private void btnToggleView_Click(object sender, EventArgs e) 433 { 434 ToggleView(); 435 } 436 chkToggleZoom_Click(object sender, EventArgs e)437 private void chkToggleZoom_Click(object sender, EventArgs e) 438 { 439 ToggleZoom(); 440 } 441 } 442 443 public enum PpuViewerMode 444 { 445 Combined = 0, 446 NametableViewer = 1, 447 ChrViewer = 2, 448 SpriteViewer = 3, 449 PaletteViewer = 4, 450 } 451 452 public interface ICompactControl 453 { GetCompactSize(bool includeMargins)454 Size GetCompactSize(bool includeMargins); ScaleImage(double scale)455 void ScaleImage(double scale); 456 } 457 } 458