diff --git a/Desktop/Form1.Designer.cs b/Desktop/Form1.Designer.cs index ab37e12..4e3bfdd 100644 --- a/Desktop/Form1.Designer.cs +++ b/Desktop/Form1.Designer.cs @@ -28,7 +28,6 @@ /// private void InitializeComponent() { - picScreen = new PictureBox(); menuStrip1 = new MenuStrip(); fileToolStripMenuItem = new ToolStripMenuItem(); openToolStripMenuItem = new ToolStripMenuItem(); @@ -44,20 +43,9 @@ resetToolStripMenuItem1 = new ToolStripMenuItem(); optionsToolStripMenuItem = new ToolStripMenuItem(); HighSpeedToolStripMenuItem = new ToolStripMenuItem(); - ((System.ComponentModel.ISupportInitialize)picScreen).BeginInit(); menuStrip1.SuspendLayout(); SuspendLayout(); // - // picScreen - // - picScreen.BackColor = Color.Black; - picScreen.Location = new Point(10, 26); - picScreen.Name = "picScreen"; - picScreen.Size = new Size(720, 576); - picScreen.SizeMode = PictureBoxSizeMode.Zoom; - picScreen.TabIndex = 0; - picScreen.TabStop = false; - // // menuStrip1 // menuStrip1.ImageScalingSize = new Size(24, 24); @@ -163,7 +151,7 @@ // HighSpeedToolStripMenuItem // HighSpeedToolStripMenuItem.Name = "HighSpeedToolStripMenuItem"; - HighSpeedToolStripMenuItem.Size = new Size(224, 26); + HighSpeedToolStripMenuItem.Size = new Size(170, 26); HighSpeedToolStripMenuItem.Text = "High Speed"; HighSpeedToolStripMenuItem.Click += btnHighSpeedToggle_Click; // @@ -172,13 +160,11 @@ AutoScaleDimensions = new SizeF(8F, 20F); AutoScaleMode = AutoScaleMode.Font; ClientSize = new Size(738, 608); - Controls.Add(picScreen); Controls.Add(menuStrip1); KeyPreview = true; MainMenuStrip = menuStrip1; Name = "Form1"; Text = "Parsons Sinclair ZX Spectrum 48K - 2026"; - ((System.ComponentModel.ISupportInitialize)picScreen).EndInit(); menuStrip1.ResumeLayout(false); menuStrip1.PerformLayout(); ResumeLayout(false); @@ -186,8 +172,6 @@ } #endregion - - private PictureBox picScreen; private MenuStrip menuStrip1; private ToolStripMenuItem fileToolStripMenuItem; private ToolStripMenuItem openToolStripMenuItem; diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs index 205655f..ca5a847 100644 --- a/Desktop/Form1.cs +++ b/Desktop/Form1.cs @@ -18,6 +18,7 @@ namespace Desktop private TapManager _tapManager = null!; private DebuggerForm? _debugger = null; private BeeperDevice _beeper = null!; + private Bitmap _screenBitmap = null!; private string _baseTitle = ""; private bool _isRunning = false; private bool _isPaused = false; @@ -34,6 +35,8 @@ namespace Desktop public Form1() { InitializeComponent(); + this.DoubleBuffered = true; + this.ResizeRedraw = true; InitializeEmulator(); } @@ -173,7 +176,10 @@ namespace Desktop { this.BeginInvoke((MethodInvoker)delegate { - UpdateScreenBitmap(); + UpdateScreenBitmap(); // Your existing method that writes the ULA data to _screenBitmap + + this.Invalidate(); // Tells Windows: "The bitmap changed, please run OnPaint()!" + this.Text = $"{_baseTitle} - FPS: {FramesPerSecond:F1}"; }); } @@ -181,10 +187,13 @@ namespace Desktop } else { - this.Invoke((MethodInvoker)delegate + this.BeginInvoke((MethodInvoker)delegate { - UpdateScreenBitmap(); - this.Text = $"{_baseTitle} - FPS: {FramesPerSecond:F1} - Tape Loaded: {tapeLoaded.ToString()}"; + UpdateScreenBitmap(); // Your existing method that writes the ULA data to _screenBitmap + + this.Invalidate(); // Tells Windows: "The bitmap changed, please run OnPaint()!" + + this.Text = $"{_baseTitle} - FPS: {FramesPerSecond:F1}"; }); } @@ -225,6 +234,40 @@ namespace Desktop }); } + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + // If we don't have a screen bitmap yet, just paint the background black and exit + if (_screenBitmap == null) + { + e.Graphics.Clear(Color.Black); + return; + } + + // 1. Calculate the scaling factor to fit the window while preserving aspect ratio + float scaleX = (float)this.ClientSize.Width / _screenBitmap.Width; + float scaleY = (float)this.ClientSize.Height / _screenBitmap.Height; + float scale = Math.Min(scaleX, scaleY); // Pick the smallest scale so it fits inside + + int newWidth = (int)(_screenBitmap.Width * scale); + int newHeight = (int)(_screenBitmap.Height * scale); + + // 2. Center the image horizontally and vertically (Pillarboxing/Letterboxing) + int posX = (this.ClientSize.Width - newWidth) / 2; + int posY = (this.ClientSize.Height - newHeight) / 2; + + // 3. RETRO MAGIC: Force Nearest-Neighbor interpolation for razor-sharp pixels! + e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; + + // PixelOffsetMode.Half is a GDI+ trick required to make NearestNeighbor perfectly accurate + e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half; + + // 4. Paint the black borders (if any) and draw the screen + e.Graphics.Clear(Color.Black); + e.Graphics.DrawImage(_screenBitmap, posX, posY, newWidth, newHeight); + } + private void HandleInstantTapeLoad() { byte[] block = _tapManager.GetNextBlock(); // Your original Queue.Dequeue() method @@ -271,7 +314,7 @@ namespace Desktop _cpu.SP++; _cpu.PC = (ushort)((pcHigh << 8) | pcLow); } - + private void UpdateScreenBitmap() { @@ -286,8 +329,14 @@ namespace Desktop Marshal.Copy(_ula.FrameBuffer, 0, bmpData.Scan0, _ula.FrameBuffer.Length); bmp.UnlockBits(bmpData); - if (picScreen.Image != null) picScreen.Image.Dispose(); - picScreen.Image = bmp; + // Dispose of the old frame to prevent massive RAM leaks! + if (_screenBitmap != null) + { + _screenBitmap.Dispose(); + } + + // Save the new frame so OnPaint() can draw it + _screenBitmap = bmp; } private void loadTAPToolStripMenuItem_Click(object sender, EventArgs e)