diff --git a/BizHawk.Client.Common/config/Config.cs b/BizHawk.Client.Common/config/Config.cs index 057ec89c0b..2603fd3329 100644 --- a/BizHawk.Client.Common/config/Config.cs +++ b/BizHawk.Client.Common/config/Config.cs @@ -509,6 +509,8 @@ namespace BizHawk.Client.Common // TAStudio public TasStateManagerSettings DefaultTasProjSettings = new TasStateManagerSettings(); + /// defaults to 0 (GDI) - on linux this is forced to GDI+ later on + public int TasStudioRenderer = 0; // Macro Tool public RecentFiles RecentMacros = new RecentFiles(8); diff --git a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj index 3b94629eba..ce2464e823 100644 --- a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj +++ b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj @@ -592,6 +592,14 @@ InputRoll.cs Component + + InputRoll.cs + Component + + + InputRoll.cs + Component + Component diff --git a/BizHawk.Client.EmuHawk/CustomControls/InputRoll.Drawing.GDI.cs b/BizHawk.Client.EmuHawk/CustomControls/InputRoll.Drawing.GDI.cs new file mode 100644 index 0000000000..c3d6f0d471 --- /dev/null +++ b/BizHawk.Client.EmuHawk/CustomControls/InputRoll.Drawing.GDI.cs @@ -0,0 +1,433 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Windows.Forms; + +using BizHawk.Client.EmuHawk.CustomControls; + +namespace BizHawk.Client.EmuHawk +{ + /// GDI32.dll related methods are abstracted to here + public partial class InputRoll + { + private readonly GDIRenderer _gdi; + private readonly IntPtr _rotatedFont; + private readonly IntPtr _normalFont; + private Size _charSize; + + #region Initialization and Destruction + + /// Initialises GDI-related stuff (called from constructor) + private void GDIConstruction() + { + using (var g = CreateGraphics()) using (_gdi.LockGraphics(g)) + _charSize = _gdi.MeasureString("A", _commonFont); //TODO make this a property so changing it updates other values + } + + private void GDIDispose() + { + _gdi.Dispose(); + GDIRenderer.DestroyHFont(_normalFont); + GDIRenderer.DestroyHFont(_rotatedFont); + } + + #endregion + + #region Drawing Methods Using GDI + + private void GDI_OnPaint(PaintEventArgs e) + { + using (_gdi.LockGraphics(e.Graphics)) + { + _gdi.StartOffScreenBitmap(Width, Height); + + // White Background + _gdi.SetBrush(Color.White); + _gdi.SetSolidPen(Color.White); + _gdi.FillRectangle(0, 0, Width, Height); + + // Lag frame calculations + SetLagFramesArray(); + + var visibleColumns = _columns.VisibleColumns.ToList(); + + if (visibleColumns.Count != 0) + { + DrawColumnBg(e, visibleColumns); + DrawColumnText(e, visibleColumns); + } + + // Background + DrawBg(e, visibleColumns); + + // Foreground + DrawData(e, visibleColumns); + + DrawColumnDrag(e); + DrawCellDrag(e); + + _gdi.CopyToScreen(); + _gdi.EndOffScreenBitmap(); + } + } + + private void GDI_DrawColumnDrag() + { + if (_columnDown != null && _columnDownMoved && _currentX.HasValue && _currentY.HasValue && IsHoveringOnColumnCell) + { + var x1 = _currentX.Value - _columnDown.Width.Value / 2; + var y1 = _currentY.Value - CellHeight / 2; + var x2 = x1 + _columnDown.Width.Value; + var y2 = y1 + CellHeight; + + _gdi.SetSolidPen(_backColor); + _gdi.DrawRectangle(x1, y1, x2, y2); + _gdi.PrepDrawString(_normalFont, _foreColor); + _gdi.DrawString(_columnDown.Text, new Point(x1 + CellWidthPadding, y1 + CellHeightPadding)); + } + } + + private void GDI_DrawCellDrag() + { + if (_draggingCell == null) return; + + var rowIndex = _draggingCell.RowIndex.Value; + var column = _draggingCell.Column; + var text = ""; + var offsetX = 0; + var offsetY = 0; + QueryItemText?.Invoke(rowIndex, column, out text, ref offsetX, ref offsetY); + + var bgColor = _backColor; + QueryItemBkColor?.Invoke(rowIndex, column, ref bgColor); + + var draggedWidth = column.Width.Value; + var draggedHeight = CellHeight; + var x1 = _currentX.Value - draggedWidth / 2; + var y1 = _currentY.Value - draggedHeight / 2; + + _gdi.SetBrush(bgColor); + _gdi.FillRectangle(x1, y1, draggedWidth, draggedHeight); + _gdi.PrepDrawString(_normalFont, _foreColor); + _gdi.DrawString(text, new Point(x1 + CellWidthPadding + offsetX, y1 + CellHeightPadding + offsetY)); + } + + private void GDI_DrawColumnText(IEnumerable visibleColumns) + { + _gdi.PrepDrawString(_normalFont, _foreColor); + var isHoveringOnColumnCell = IsHoveringOnColumnCell; + if (HorizontalOrientation) + { + var start = -_vBar.Value; + foreach (var column in visibleColumns) + { + var point = new Point(CellWidthPadding, start + CellHeightPadding); + if (isHoveringOnColumnCell && column == CurrentCell.Column) + { + _gdi.PrepDrawString(_normalFont, SystemColors.HighlightText); + _gdi.DrawString(column.Text, point); + _gdi.PrepDrawString(_normalFont, _foreColor); + } + else + { + _gdi.DrawString(column.Text, point); + } + start += CellHeight; + } + } + else + { + var paddingX = 2 * CellWidthPadding - _hBar.Value; //TODO fix this CellPadding issue (2 * CellPadding vs just CellPadding) + foreach (var column in visibleColumns) + { + var point = new Point(column.Left.Value + paddingX, CellHeightPadding); + if (isHoveringOnColumnCell && column == CurrentCell.Column) + { + _gdi.PrepDrawString(_normalFont, SystemColors.HighlightText); + _gdi.DrawString(column.Text, point); + _gdi.PrepDrawString(_normalFont, _foreColor); + } + else + { + _gdi.DrawString(column.Text, point); + } + } + } + } + + private void GDI_DrawData(IReadOnlyList visibleColumns) + { + if (visibleColumns.Count == 0) return; // Prevent exceptions with small TAStudio windows + if (QueryItemText == null) return; + + _gdi.PrepDrawString(_normalFont, _foreColor); + + var startRow = FirstVisibleRow; + var range = Math.Min(LastVisibleRow, RowCount - 1) - startRow + 1; + int LastVisible; + if (HorizontalOrientation) + { + for (int i = 0, f = 0; f < range; i++, f++) + { + f += _lagFrames[i]; + LastVisible = LastVisibleColumnIndex; + for (var j = FirstVisibleColumn; j <= LastVisible; j++) + { + Bitmap image = null; + var bitmapOffsetX = 0; + var bitmapOffsetY = 0; + QueryItemIcon?.Invoke(f + startRow, visibleColumns[j], ref image, ref bitmapOffsetX, ref bitmapOffsetY); + + if (image != null) + { + _gdi.DrawBitmap( + image, + new Point(RowsToPixels(i) + CellWidthPadding + bitmapOffsetX, j * CellHeight + CellHeightPadding * 2 + bitmapOffsetY), + true + ); + } + + string text; + var strOffsetX = 0; + var strOffsetY = 0; + QueryItemText(f + startRow, visibleColumns[j], out text, ref strOffsetX, ref strOffsetY); + + var rePrep = j == 1; + if (rePrep) + { + // 1. not sure about this; 2. repreps may be excess, but if we render one column at a time, we do need to change back after rendering the header + _gdi.PrepDrawString( + _rotatedFont, + _selectedItems.Contains(new Cell { Column = visibleColumns[j], RowIndex = i + startRow }) + ? SystemColors.HighlightText + : _foreColor + ); + } + + // Centre Text + var point = new Point( + RowsToPixels(i) + (CellWidth - text.Length * _charSize.Width) / 2 + strOffsetX, + j * CellHeight + CellHeightPadding - _vBar.Value + strOffsetY + ); + if (!string.IsNullOrWhiteSpace(text)) _gdi.DrawString(text, point); + + if (rePrep) _gdi.PrepDrawString(_normalFont, _foreColor); + } + } + } + else + { + var xPadding = CellWidthPadding + 1 - _hBar.Value; + for (int i = 0, f = 0; f < range; i++, f++) // Vertical + { + f += _lagFrames[i]; + LastVisible = LastVisibleColumnIndex; + var y = RowsToPixels(i) + CellHeightPadding; + for (var j = FirstVisibleColumn; j <= LastVisible; j++) // Horizontal + { + var column = visibleColumns[j]; + var x = column.Left.Value + xPadding; + Bitmap image = null; + var bitmapOffsetX = 0; + var bitmapOffsetY = 0; + QueryItemIcon?.Invoke(f + startRow, column, ref image, ref bitmapOffsetX, ref bitmapOffsetY); + + if (image != null) + { + _gdi.DrawBitmap( + image, + new Point(x + bitmapOffsetX, y + bitmapOffsetY + CellHeightPadding), + true + ); + } + + string text; + var strOffsetX = 0; + var strOffsetY = 0; + QueryItemText(f + startRow, column, out text, ref strOffsetX, ref strOffsetY); + + var rePrep = !_selectedItems.Contains(new Cell { Column = column, RowIndex = f + startRow }); + + if (rePrep) _gdi.PrepDrawString(_normalFont, SystemColors.HighlightText); + + if (!string.IsNullOrWhiteSpace(text)) _gdi.DrawString(text, new Point(x + strOffsetX, y + strOffsetY)); + + if (rePrep) _gdi.PrepDrawString(_normalFont, _foreColor); + } + } + } + } + + private void GDI_DrawColumnBg(IReadOnlyList visibleColumns) + { + var columnCount = visibleColumns.Count; + + _gdi.SetBrush(SystemColors.ControlLight); + _gdi.SetSolidPen(Color.Black); + + if (HorizontalOrientation) + { + _gdi.FillRectangle(0, 0, ColumnWidth + 1, DrawHeight + 1); + _gdi.Line(0, 0, 0, columnCount * CellHeight + 1); + _gdi.Line(ColumnWidth, 0, ColumnWidth, columnCount * CellHeight + 1); + + if (columnCount != 0) for (int i = 0, y = -_vBar.Value; i <= columnCount; i++, y += CellHeight) + { + _gdi.Line(1, y, ColumnWidth, y); + } + } + else + { + var bottomEdge = RowsToPixels(0); + + // Gray column box and black line underneath + _gdi.FillRectangle(0, 0, Width + 1, bottomEdge + 1); + _gdi.Line(0, 0, TotalColWidth.Value + 1, 0); + _gdi.Line(0, bottomEdge, TotalColWidth.Value + 1, bottomEdge); + + // Vertical black seperators + foreach (var column in visibleColumns) + { + var x = column.Left.Value - _hBar.Value; + _gdi.Line(x, 0, x, bottomEdge); + } + if (columnCount != 0) + { + var x = TotalColWidth.Value - _hBar.Value; + _gdi.Line(x, 0, x, bottomEdge); + } + } + + // Emphasis + var columnHeight = ColumnHeight - 1; + if (HorizontalOrientation) + { + for (var i = 0; i < columnCount; i++) + { + if (!visibleColumns[i].Emphasis) continue; // only act on emphasised columns + + _gdi.SetBrush(SystemColors.ActiveBorder); + _gdi.FillRectangle(1, i * CellHeight + 1, ColumnWidth - 1, columnHeight); + } + } + else + { + foreach (var column in visibleColumns) + { + if (!column.Emphasis) continue; // only act on emphasised columns + + _gdi.SetBrush(SystemColors.ActiveBorder); + _gdi.FillRectangle(column.Left.Value + 1 - _hBar.Value, 1, column.Width.Value - 1, columnHeight); + } + } + + // If the user is hovering over a column + if (IsHoveringOnColumnCell) + { + if (HorizontalOrientation) + { + for (var i = 0; i < columnCount; i++) + { + var column = visibleColumns[i]; + if (column != CurrentCell.Column) continue; // only act on selected + + _gdi.SetBrush(column.Emphasis ? Add(SystemColors.Highlight, 0x00222222) : SystemColors.Highlight); + _gdi.FillRectangle(1, i * CellHeight + 1, ColumnWidth - 1, columnHeight); + } + } + else + { + //TODO multiple selected columns + foreach (var column in visibleColumns) + { + if (column != CurrentCell.Column) continue; // only act on selected + if (column.Left.Value - _hBar.Value > Width || column.Right.Value - _hBar.Value < 0) continue; // Left of column is to the right of the viewable area, or right of column is to the left of the viewable area + + _gdi.SetBrush(column.Emphasis ? Add(SystemColors.Highlight, 0x00550000) : SystemColors.Highlight); + var left = column.Left.Value + 1; + _gdi.FillRectangle(left - _hBar.Value, 1, column.Right.Value - left, columnHeight); + } + } + } + } + + private void GDI_DrawBg(PaintEventArgs e, List visibleColumns) + { + if (UseCustomBackground && QueryItemBkColor != null) DoBackGroundCallback(e, visibleColumns); + + if (GridLines) + { + _gdi.SetSolidPen(SystemColors.ControlLight); + if (HorizontalOrientation) + { + // Columns + var iLimit = VisibleRows + 1; + for (var i = 1; i < iLimit; i++) + { + var x = RowsToPixels(i); + _gdi.Line(x, 1, x, DrawHeight); + } + + // Rows + var x1 = RowsToPixels(0) + 1; + var jLimit = visibleColumns.Count + 1; + for (var j = 0; j < jLimit; j++) _gdi.Line(x1, j * CellHeight - _vBar.Value, DrawWidth, j * CellHeight - _vBar.Value); + } + else + { + // Columns + var y1 = ColumnHeight + 1; + var y2 = Height - 1; + foreach (var column in visibleColumns) + { + var x = column.Left.Value - _hBar.Value; + _gdi.Line(x, y1, x, y2); + } + if (visibleColumns.Count != 0) + { + var x = TotalColWidth.Value - _hBar.Value; + _gdi.Line(x, y1, x, y2); + } + + // Rows + var x2 = Width + 1; + var iLimit = VisibleRows + 1; + for (var i = 1; i < iLimit; i++) + { + var y = RowsToPixels(i); + _gdi.Line(0, y, x2, y); + } + } + } + + if (_selectedItems.Count != 0) DoSelectionBG(e, visibleColumns); + } + + private void GDI_DrawCellBG(Color color, Cell cell, IList visibleColumns) + { + // We can't draw without row and column, so assume they exist and fail catastrophically if they don't + int x, y, w; + if (HorizontalOrientation) + { + x = RowsToPixels(cell.RowIndex.Value) + 1; + if (x < ColumnWidth) return; + y = CellHeight * visibleColumns.IndexOf(cell.Column) + 1 - _vBar.Value; + w = CellWidth - 1; + } + else + { + y = RowsToPixels(cell.RowIndex.Value) + 1; + if (y < ColumnHeight) return; + x = cell.Column.Left.Value - _hBar.Value + 1; + w = cell.Column.Width.Value - 1; + } + if (x > DrawWidth || y > DrawHeight) return; // Don't draw if off-screen. + + _gdi.SetBrush(color); + _gdi.FillRectangle(x, y, w, CellHeight - 1); + } + + #endregion + } +} diff --git a/BizHawk.Client.EmuHawk/CustomControls/InputRoll.Drawing.GDIP.cs b/BizHawk.Client.EmuHawk/CustomControls/InputRoll.Drawing.GDIP.cs new file mode 100644 index 0000000000..8137ff9e1b --- /dev/null +++ b/BizHawk.Client.EmuHawk/CustomControls/InputRoll.Drawing.GDIP.cs @@ -0,0 +1,477 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Windows.Forms; + +namespace BizHawk.Client.EmuHawk +{ + /// New GDI+ methods live here + public partial class InputRoll + { + /// instance field to mirror GDI implementation + private Pen sPen; + + /// instance field to mirror GDI implementation + private Brush sBrush; + + /// GDI+ uses floats to measure strings + private SizeF _charSizeF; + + #region Initialization and Destruction + + /// Initialises GDI+-related stuff (called from constructor) + private void GDIPConstruction() + { + // HFont? + // Rotated HFont? + + SetStyle(ControlStyles.AllPaintingInWmPaint, true); + SetStyle(ControlStyles.UserPaint, true); + SetStyle(ControlStyles.SupportsTransparentBackColor, true); + SetStyle(ControlStyles.Opaque, true); + SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + + using (var g = CreateGraphics()) + { + _charSizeF = g.MeasureString("A", _commonFont); +// _charSize = Size.Round(sizeF); + } + } + + #endregion + + #region Drawing Methods Using GDI+ + + private void GDIP_OnPaint(PaintEventArgs e) + { + // white background + sBrush = new SolidBrush(Color.White); + sPen = new Pen(Color.White); + + e.Graphics.FillRectangle(sBrush, e.ClipRectangle); + e.Graphics.Flush(); + + // Lag frame calculations + SetLagFramesArray(); + + var visibleColumns = _columns.VisibleColumns.ToList(); + + if (visibleColumns.Count != 0) + { + DrawColumnBg(e, visibleColumns); + DrawColumnText(e, visibleColumns); + } + + // Background + DrawBg(e, visibleColumns); + + // Foreground + DrawData(e, visibleColumns); + + DrawColumnDrag(e); + DrawCellDrag(e); + } + + private void GDIP_DrawColumnDrag(PaintEventArgs e) + { + if (_columnDown != null && _columnDownMoved && _currentX.HasValue && _currentY.HasValue && IsHoveringOnColumnCell) + { + var x1 = _currentX.Value - _columnDown.Width.Value / 2; + var y1 = _currentY.Value - CellHeight / 2; + var x2 = x1 + _columnDown.Width.Value; + var y2 = y1 + CellHeight; + + sPen = new Pen(_backColor); + e.Graphics.DrawRectangle(sPen, x1, y1, x2, y2); + sBrush = new SolidBrush(_foreColor); +// e.Graphics.DrawString(_columnDown.Text, _commonFont, sBrush, (PointF)(new Point(x1 + CellWidthPadding, y1 + CellHeightPadding))); + GDIP_DrawString(e, _columnDown.Text, _commonFont, new Point(x1 + CellWidthPadding, y1 + CellHeightPadding), _foreColor); + } + } + + private void GDIP_DrawCellDrag(PaintEventArgs e) + { + if (_draggingCell == null) return; + + var text = ""; + var offsetX = 0; + var offsetY = 0; + QueryItemText?.Invoke(_draggingCell.RowIndex.Value, _draggingCell.Column, out text, ref offsetX, ref offsetY); + + var bgColor = _backColor; + QueryItemBkColor?.Invoke(_draggingCell.RowIndex.Value, _draggingCell.Column, ref bgColor); + + var x1 = _currentX.Value - _draggingCell.Column.Width.Value / 2; + var y1 = _currentY.Value - CellHeight / 2; + var x2 = x1 + _draggingCell.Column.Width.Value; + var y2 = y1 + CellHeight; + + sBrush = new SolidBrush(bgColor); + e.Graphics.FillRectangle(sBrush, x1, y1, x2 - x1, y2 - y1); + sBrush = new SolidBrush(_foreColor); +// e.Graphics.DrawString(text, _commonFont, sBrush, (PointF)(new Point(x1 + CellWidthPadding + offsetX, y1 + CellHeightPadding + offsetY))); + GDIP_DrawString(e, text, _commonFont, new Point(x1 + CellWidthPadding + offsetX, y1 + CellHeightPadding + offsetY), _foreColor); + } + + private void GDIP_DrawColumnText(PaintEventArgs e, IReadOnlyCollection visibleColumns) + { + sBrush = new SolidBrush(_foreColor); + if (HorizontalOrientation) + { + var start = -_vBar.Value; + + foreach (var column in visibleColumns) + { + var point = new Point(CellWidthPadding, start + CellHeightPadding); + + if (IsHoveringOnColumnCell && column == CurrentCell.Column) + { + var temp = sBrush; + sBrush = new SolidBrush(SystemColors.HighlightText); +// e.Graphics.DrawString(column.Text, _commonFont, sBrush, (PointF)(point)); + GDIP_DrawString(e, column.Text, _commonFont, point, SystemColors.HighlightText); + sBrush = temp; + } + else + { +// e.Graphics.DrawString(column.Text, _commonFont, sBrush, (PointF)(point)); + GDIP_DrawString(e, column.Text, _commonFont, point, _foreColor); + } + + start += CellHeight; + } + } + else + { + var xPadding = CellWidthPadding + 1 - _hBar.Value; + + foreach (var column in visibleColumns) + { + var point = new Point(column.Left.Value + xPadding, CellHeightPadding); + + if (IsHoveringOnColumnCell && column == CurrentCell.Column) + { + var temp = sBrush; + sBrush = new SolidBrush(SystemColors.HighlightText); +// e.Graphics.DrawString(column.Text, _commonFont, sBrush, (PointF)(point)); + GDIP_DrawString(e, column.Text, _commonFont, point, SystemColors.HighlightText); + sBrush = temp; + } + else + { +// e.Graphics.DrawString(column.Text, _commonFont, sBrush, (PointF)(point)); + GDIP_DrawString(e, column.Text, _commonFont, point, _foreColor); + } + } + } + } + + private void GDIP_DrawData(PaintEventArgs e, IReadOnlyList visibleColumns) + { + if (visibleColumns.Count == 0) return; // Prevent exceptions with small TAStudio windows + if (QueryItemText == null) return; + + var startRow = FirstVisibleRow; + int LastVisible; + if (HorizontalOrientation) + { + var isRotated = false; + var fLimit = Math.Min(LastVisibleRow, RowCount - 1) - startRow + 1; + + sBrush = new SolidBrush(_foreColor); + for (int i = 0, f = 0; f < fLimit; i++, f++) + { + f += _lagFrames[i]; + LastVisible = LastVisibleColumnIndex; + for (var j = FirstVisibleColumn; j <= LastVisible; j++) + { + Bitmap image = null; + var bitmapOffsetX = 0; + var bitmapOffsetY = 0; + QueryItemIcon?.Invoke(f + startRow, visibleColumns[j], ref image, ref bitmapOffsetX, ref bitmapOffsetY); + + if (image != null) + { + var x1 = RowsToPixels(i) + CellWidthPadding + bitmapOffsetX; + var y1 = j * CellHeight + CellHeightPadding * 2 + bitmapOffsetY; + e.Graphics.DrawImage(image, new Point(x1, y1)); + } + + string text; + var strOffsetX = 0; + var strOffsetY = 0; + QueryItemText(f + startRow, visibleColumns[j], out text, ref strOffsetX, ref strOffsetY); + + // Centre Text + var point = new Point( + RowsToPixels(i) + (CellWidth - (int)Math.Round(text.Length * _charSizeF.Width)) / 2 + strOffsetX, + j * CellHeight + CellHeightPadding - _vBar.Value + strOffsetY + ); + + var rePrep = false; + if (j == 1) + { + if (_selectedItems.Contains(new Cell { Column = visibleColumns[j], RowIndex = i + startRow })) + { + isRotated = true; + sBrush = new SolidBrush(SystemColors.HighlightText); + rePrep = true; + } + else if (j == 1) + { + // 1. not sure about this; 2. repreps may be excess, but if we render one column at a time, we do need to change back after rendering the header + rePrep = true; + isRotated = true; + sBrush = new SolidBrush(SystemColors.HighlightText); + } + } + + if (!string.IsNullOrWhiteSpace(text)) + { +// _gdi.DrawString(text, point); + if (isRotated) + { + var sz = e.Graphics.VisibleClipBounds.Size; + e.Graphics.TranslateTransform(sz.Width / 2, sz.Height / 2); + e.Graphics.RotateTransform(90); + sz = e.Graphics.MeasureString(text, _commonFont); + e.Graphics.DrawString(text, _commonFont, sBrush, -(sz.Width / 2), -(sz.Height / 2)); + } + else + { +// e.Graphics.DrawString(text, _commonFont, sBrush, (PointF)point); + GDIP_DrawString(e, text, _commonFont, point, new Pen(sBrush).Color); + } + } + + if (rePrep) + { + isRotated = false; + sBrush = new SolidBrush(_foreColor); + } + } + } + } + else + { + var range = Math.Min(LastVisibleRow, RowCount - 1) - startRow + 1; + var xPadding = CellWidthPadding + 1 - _hBar.Value; + sBrush = new SolidBrush(_foreColor); + for (int i = 0, f = 0; f < range; i++, f++) // Vertical + { + f += _lagFrames[i]; + LastVisible = LastVisibleColumnIndex; + for (var j = FirstVisibleColumn; j <= LastVisible; j++) // Horizontal + { + var col = visibleColumns[j]; + + Bitmap image = null; + var bitmapOffsetX = 0; + var bitmapOffsetY = 0; + QueryItemIcon?.Invoke(f + startRow, visibleColumns[j], ref image, ref bitmapOffsetX, ref bitmapOffsetY); + + var point = new Point(col.Left.Value + xPadding, RowsToPixels(i) + CellHeightPadding); + if (image != null) e.Graphics.DrawImage(image, new Point(point.X + bitmapOffsetX, point.Y + bitmapOffsetY + CellHeightPadding)); + + string text; + var strOffsetX = 0; + var strOffsetY = 0; + QueryItemText(f + startRow, visibleColumns[j], out text, ref strOffsetX, ref strOffsetY); + + var rePrep = false; + if (_selectedItems.Contains(new Cell { Column = visibleColumns[j], RowIndex = f + startRow })) + { + sBrush = new SolidBrush(SystemColors.HighlightText); + rePrep = true; + } + + if (!string.IsNullOrWhiteSpace(text)) + { +// e.Graphics.DrawString(text, _commonFont, sBrush, (PointF)(new Point(point.X + strOffsetX, point.Y + strOffsetY))); + GDIP_DrawString(e, text, _commonFont, new Point(point.X + strOffsetX, point.Y + strOffsetY), new Pen(sBrush).Color); + } + + if (rePrep) sBrush = new SolidBrush(_foreColor); + } + } + } + } + + private void GDIP_DrawColumnBg(PaintEventArgs e, IReadOnlyList visibleColumns) + { + var columnCount = visibleColumns.Count; + sBrush = new SolidBrush(SystemColors.ControlLight); + sPen = new Pen(Color.Black); + + if (HorizontalOrientation) + { + e.Graphics.FillRectangle(sBrush, 0, 0, ColumnWidth + 1, DrawHeight + 1); + e.Graphics.DrawLine(sPen, 0, 0, 0, columnCount * CellHeight + 1); + e.Graphics.DrawLine(sPen, ColumnWidth, 0, ColumnWidth, columnCount * CellHeight + 1); + + var iLimit = columnCount + 1; + if (iLimit > 1) + { + for (int i = 0, y = -_vBar.Value; i < iLimit; i++, y += CellHeight) e.Graphics.DrawLine(sPen, 1, y, ColumnWidth, y); + } + } + else + { + var bottomEdge = RowsToPixels(0); + + // Gray column box and black line underneath + e.Graphics.FillRectangle(sBrush, 0, 0, Width + 1, bottomEdge + 1); + e.Graphics.DrawLine(sPen, 0, 0, TotalColWidth.Value + 1, 0); + e.Graphics.DrawLine(sPen, 0, bottomEdge, TotalColWidth.Value + 1, bottomEdge); + + // Vertical black seperators + foreach (var column in visibleColumns) + { + var pos = column.Left.Value - _hBar.Value; + e.Graphics.DrawLine(sPen, pos, 0, pos, bottomEdge); + } + if (columnCount != 0) + { + var right = TotalColWidth.Value - _hBar.Value; + e.Graphics.DrawLine(sPen, right, 0, right, bottomEdge); + } + } + + // Emphasis + for (var i = 0; i < columnCount; i++) + { + var column = visibleColumns[i]; + if (!column.Emphasis) continue; // only act on emphasised columns + + sBrush = new SolidBrush(SystemColors.ActiveBorder); + if (HorizontalOrientation) e.Graphics.FillRectangle(sBrush, 1, i * CellHeight + 1, ColumnWidth - 1, ColumnHeight - 1); + else e.Graphics.FillRectangle(sBrush, column.Left.Value + 1 - _hBar.Value, 1, column.Width.Value - 1, ColumnHeight - 1); + } + + // If the user is hovering over a column + if (IsHoveringOnColumnCell) + { + if (HorizontalOrientation) + { + for (var i = 0; i < columnCount; i++) + { + var column = visibleColumns[i]; + if (column != CurrentCell.Column) continue; // only act on selected + + sBrush = new SolidBrush(column.Emphasis + ? Color.FromArgb(SystemColors.Highlight.ToArgb() + 0x00222222) //TODO should be bitwise or? + : SystemColors.Highlight); + + e.Graphics.FillRectangle(sBrush, 1, i * CellHeight + 1, ColumnWidth - 1, ColumnHeight - 1); + } + } + else + { + //TODO multiple selected columns + foreach (var column in visibleColumns) + { + if (column != CurrentCell.Column) continue; // only act on selected + if (column.Left.Value - _hBar.Value > Width || column.Right.Value - _hBar.Value < 0) continue; // Left of column is to the right of the viewable area, or right of column is to the left of the viewable area + + var left = column.Left.Value - _hBar.Value; + var width = column.Right.Value - _hBar.Value - left; + sBrush = new SolidBrush(column.Emphasis + ? Color.FromArgb(SystemColors.Highlight.ToArgb() + 0x00550000) //TODO should be bitwise or? + : SystemColors.Highlight); + + e.Graphics.FillRectangle(sBrush, left + 1, 1, width - 1, ColumnHeight - 1); + } + } + } + } + + private void GDIP_DrawBg(PaintEventArgs e, List visibleColumns) + { + if (UseCustomBackground && QueryItemBkColor != null) DoBackGroundCallback(e, visibleColumns); + + if (GridLines) + { + sPen = new Pen(SystemColors.ControlLight); + if (HorizontalOrientation) + { + // Columns + var iLimit = VisibleRows + 1; + for (var i = 1; i < iLimit; i++) + { + var x = RowsToPixels(i); + e.Graphics.DrawLine(sPen, x, 1, x, DrawHeight); + } + + // Rows + var x1 = RowsToPixels(0) + 1; + var jLimit = visibleColumns.Count + 1; + for (var j = 0; j < jLimit; j++) + { + var y = j * CellHeight - _vBar.Value; + e.Graphics.DrawLine(sPen, x1, y, DrawWidth, y); + } + } + else + { + // Columns + var y1 = ColumnHeight + 1; + var y2 = Height - 1; + foreach (var column in visibleColumns) + { + var x = column.Left.Value - _hBar.Value; + e.Graphics.DrawLine(sPen, x, y1, x, y2); + } + if (visibleColumns.Count != 0) + { + var x = TotalColWidth.Value - _hBar.Value; + e.Graphics.DrawLine(sPen, x, y1, x, y2); + } + + // Rows + var x2 = Width + 1; + var iLimit = VisibleRows + 1; + for (var i = 1; i < iLimit; i++) + { + var y = RowsToPixels(i); + e.Graphics.DrawLine(sPen, 0, y, x2, y); + } + } + } + + if (_selectedItems.Count != 0) DoSelectionBG(e, visibleColumns); + } + + private void GDIP_DrawCellBG(PaintEventArgs e, Color color, Cell cell, IList visibleColumns) + { + // We can't draw without row and column, so assume they exist and fail catastrophically if they don't + int x, y, w; + if (HorizontalOrientation) + { + x = RowsToPixels(cell.RowIndex.Value) + 1; + if (x < ColumnWidth) return; + y = CellHeight * visibleColumns.IndexOf(cell.Column) + 1 - _vBar.Value; + w = CellWidth - 1; + } + else + { + y = RowsToPixels(cell.RowIndex.Value) + 1; + if (y < ColumnHeight) return; + x = cell.Column.Left.Value - _hBar.Value + 1; + w = cell.Column.Width.Value - 1; + } + if (x > DrawWidth || y > DrawHeight) return; // Don't draw if off-screen. + + sBrush = new SolidBrush(color.A == 0 ? Color.FromArgb(255, color) : color); + e.Graphics.FillRectangle(sBrush, x, y, w, CellHeight - 1); + } + + private void GDIP_DrawString(PaintEventArgs e, string text, Font font, Point point, Color color) + { +// TextRenderer.DrawText(e.Graphics, text, font, point, color); + e.Graphics.DrawString(text, font, new SolidBrush(color), (PointF)point); + } + + #endregion + } +} diff --git a/BizHawk.Client.EmuHawk/CustomControls/InputRoll.Drawing.cs b/BizHawk.Client.EmuHawk/CustomControls/InputRoll.Drawing.cs index b5a2556f7b..452703033d 100644 --- a/BizHawk.Client.EmuHawk/CustomControls/InputRoll.Drawing.cs +++ b/BizHawk.Client.EmuHawk/CustomControls/InputRoll.Drawing.cs @@ -6,380 +6,59 @@ using System.Windows.Forms; namespace BizHawk.Client.EmuHawk { + /// + /// This in the most part now contains renderer selection logic + /// public partial class InputRoll { + #region Renderer-Based Logic Methods + protected override void OnPaint(PaintEventArgs e) { - using (_gdi.LockGraphics(e.Graphics)) - { - _gdi.StartOffScreenBitmap(Width, Height); - - // White Background - _gdi.SetBrush(Color.White); - _gdi.SetSolidPen(Color.White); - _gdi.FillRectangle(0, 0, Width, Height); - - // Lag frame calculations - SetLagFramesArray(); - - var visibleColumns = _columns.VisibleColumns.ToList(); - - if (visibleColumns.Any()) - { - DrawColumnBg(e, visibleColumns); - DrawColumnText(e, visibleColumns); - } - - // Background - DrawBg(e, visibleColumns); - - // Foreground - DrawData(e, visibleColumns); - - DrawColumnDrag(e); - DrawCellDrag(e); - - _gdi.CopyToScreen(); - _gdi.EndOffScreenBitmap(); - } - } - - protected override void OnPaintBackground(PaintEventArgs pevent) - { - // Do nothing, and this should never be called + if (Renderer == RollRenderer.GDIPlus) + GDIP_OnPaint(e); + else if (Renderer == RollRenderer.GDI) + GDI_OnPaint(e); } private void DrawColumnDrag(PaintEventArgs e) { - if (_columnDown != null && _columnDownMoved && _currentX.HasValue && _currentY.HasValue && IsHoveringOnColumnCell) - { - int x1 = _currentX.Value - (_columnDown.Width.Value / 2); - int y1 = _currentY.Value - (CellHeight / 2); - int x2 = x1 + _columnDown.Width.Value; - int y2 = y1 + CellHeight; - - _gdi.SetSolidPen(_backColor); - _gdi.DrawRectangle(x1, y1, x2, y2); - _gdi.PrepDrawString(_normalFont, _foreColor); - _gdi.DrawString(_columnDown.Text, new Point(x1 + CellWidthPadding, y1 + CellHeightPadding)); - } + if (Renderer == RollRenderer.GDIPlus) + GDIP_DrawColumnDrag(e); + else if (Renderer == RollRenderer.GDI) + GDI_DrawColumnDrag(); } private void DrawCellDrag(PaintEventArgs e) { - if (_draggingCell != null) - { - var text = ""; - int offsetX = 0; - int offsetY = 0; - QueryItemText?.Invoke(_draggingCell.RowIndex.Value, _draggingCell.Column, out text, ref offsetX, ref offsetY); - - Color bgColor = _backColor; - QueryItemBkColor?.Invoke(_draggingCell.RowIndex.Value, _draggingCell.Column, ref bgColor); - - int x1 = _currentX.Value - (_draggingCell.Column.Width.Value / 2); - int y1 = _currentY.Value - (CellHeight / 2); - int x2 = x1 + _draggingCell.Column.Width.Value; - int y2 = y1 + CellHeight; - - - _gdi.SetBrush(bgColor); - _gdi.FillRectangle(x1, y1, x2 - x1, y2 - y1); - _gdi.PrepDrawString(_normalFont, _foreColor); - _gdi.DrawString(text, new Point(x1 + CellWidthPadding + offsetX, y1 + CellHeightPadding + offsetY)); - } + if (Renderer == RollRenderer.GDIPlus) + GDIP_DrawCellDrag(e); + else if (Renderer == RollRenderer.GDI) + GDIP_DrawCellDrag(e); } private void DrawColumnText(PaintEventArgs e, List visibleColumns) { - if (HorizontalOrientation) - { - int start = -_vBar.Value; - - _gdi.PrepDrawString(_normalFont, _foreColor); - - foreach (var column in visibleColumns) - { - var point = new Point(CellWidthPadding, start + CellHeightPadding); - - if (IsHoveringOnColumnCell && column == CurrentCell.Column) - { - _gdi.PrepDrawString(_normalFont, SystemColors.HighlightText); - _gdi.DrawString(column.Text, point); - _gdi.PrepDrawString(_normalFont, _foreColor); - } - else - { - _gdi.DrawString(column.Text, point); - } - - start += CellHeight; - } - } - else - { - _gdi.PrepDrawString(_normalFont, _foreColor); - - foreach (var column in visibleColumns) - { - var point = new Point(column.Left.Value + 2 * CellWidthPadding - _hBar.Value, CellHeightPadding); // TODO: fix this CellPadding issue (2 * CellPadding vs just CellPadding) - - if (IsHoveringOnColumnCell && column == CurrentCell.Column) - { - _gdi.PrepDrawString(_normalFont, SystemColors.HighlightText); - _gdi.DrawString(column.Text, point); - _gdi.PrepDrawString(_normalFont, _foreColor); - } - else - { - _gdi.DrawString(column.Text, point); - } - } - } + if (Renderer == RollRenderer.GDIPlus) + GDIP_DrawColumnText(e, visibleColumns); + else if (Renderer == RollRenderer.GDI) + GDI_DrawColumnText(visibleColumns); } private void DrawData(PaintEventArgs e, List visibleColumns) { - // Prevent exceptions with small TAStudio windows - if (visibleColumns.Count == 0) - { - return; - } - if (QueryItemText != null) - { - if (HorizontalOrientation) - { - int startRow = FirstVisibleRow; - int range = Math.Min(LastVisibleRow, RowCount - 1) - startRow + 1; - - _gdi.PrepDrawString(_normalFont, _foreColor); - for (int i = 0, f = 0; f < range; i++, f++) - { - f += _lagFrames[i]; - int LastVisible = LastVisibleColumnIndex; - for (int j = FirstVisibleColumn; j <= LastVisible; j++) - { - Bitmap image = null; - int x = 0; - int y = 0; - int bitmapOffsetX = 0; - int bitmapOffsetY = 0; - - QueryItemIcon?.Invoke(f + startRow, visibleColumns[j], ref image, ref bitmapOffsetX, ref bitmapOffsetY); - - if (image != null) - { - x = RowsToPixels(i) + CellWidthPadding + bitmapOffsetX; - y = (j * CellHeight) + (CellHeightPadding * 2) + bitmapOffsetY; - _gdi.DrawBitmap(image, new Point(x, y), true); - } - - string text; - int strOffsetX = 0; - int strOffsetY = 0; - QueryItemText(f + startRow, visibleColumns[j], out text, ref strOffsetX, ref strOffsetY); - - // Center Text - x = RowsToPixels(i) + ((CellWidth - (text.Length * _charSize.Width)) / 2); - y = (j * CellHeight) + CellHeightPadding - _vBar.Value; - var point = new Point(x + strOffsetX, y + strOffsetY); - - var rePrep = false; - if (j == 1) - if (_selectedItems.Contains(new Cell { Column = visibleColumns[j], RowIndex = i + startRow })) - { - _gdi.PrepDrawString(_rotatedFont, SystemColors.HighlightText); - rePrep = true; - } - else if (j == 1) - { - // 1. not sure about this; 2. repreps may be excess, but if we render one column at a time, we do need to change back after rendering the header - rePrep = true; - _gdi.PrepDrawString(_rotatedFont, _foreColor); - } - - if (!string.IsNullOrWhiteSpace(text)) - { - _gdi.DrawString(text, point); - } - - if (rePrep) - { - _gdi.PrepDrawString(_normalFont, _foreColor); - } - } - } - } - else - { - int startRow = FirstVisibleRow; - int range = Math.Min(LastVisibleRow, RowCount - 1) - startRow + 1; - - _gdi.PrepDrawString(_normalFont, _foreColor); - int xPadding = CellWidthPadding + 1 - _hBar.Value; - for (int i = 0, f = 0; f < range; i++, f++) // Vertical - { - f += _lagFrames[i]; - int LastVisible = LastVisibleColumnIndex; - for (int j = FirstVisibleColumn; j <= LastVisible; j++) // Horizontal - { - RollColumn col = visibleColumns[j]; - - string text; - int strOffsetX = 0; - int strOffsetY = 0; - Point point = new Point(col.Left.Value + xPadding, RowsToPixels(i) + CellHeightPadding); - - Bitmap image = null; - int bitmapOffsetX = 0; - int bitmapOffsetY = 0; - - QueryItemIcon?.Invoke(f + startRow, visibleColumns[j], ref image, ref bitmapOffsetX, ref bitmapOffsetY); - - if (image != null) - { - _gdi.DrawBitmap(image, new Point(point.X + bitmapOffsetX, point.Y + bitmapOffsetY + CellHeightPadding), true); - } - - QueryItemText(f + startRow, visibleColumns[j], out text, ref strOffsetX, ref strOffsetY); - - bool rePrep = false; - if (_selectedItems.Contains(new Cell { Column = visibleColumns[j], RowIndex = f + startRow })) - { - _gdi.PrepDrawString(_normalFont, SystemColors.HighlightText); - rePrep = true; - } - - if (!string.IsNullOrWhiteSpace(text)) - { - _gdi.DrawString(text, new Point(point.X + strOffsetX, point.Y + strOffsetY)); - } - - if (rePrep) - { - _gdi.PrepDrawString(_normalFont, _foreColor); - } - } - } - } - } + if (Renderer == RollRenderer.GDIPlus) + GDIP_DrawData(e, visibleColumns); + else if (Renderer == RollRenderer.GDI) + GDI_DrawData(visibleColumns); } private void DrawColumnBg(PaintEventArgs e, List visibleColumns) { - _gdi.SetBrush(SystemColors.ControlLight); - _gdi.SetSolidPen(Color.Black); - - if (HorizontalOrientation) - { - _gdi.FillRectangle(0, 0, ColumnWidth + 1, DrawHeight + 1); - _gdi.Line(0, 0, 0, visibleColumns.Count * CellHeight + 1); - _gdi.Line(ColumnWidth, 0, ColumnWidth, visibleColumns.Count * CellHeight + 1); - - int start = -_vBar.Value; - foreach (var column in visibleColumns) - { - _gdi.Line(1, start, ColumnWidth, start); - start += CellHeight; - } - - if (visibleColumns.Any()) - { - _gdi.Line(1, start, ColumnWidth, start); - } - } - else - { - int bottomEdge = RowsToPixels(0); - - // Gray column box and black line underneath - _gdi.FillRectangle(0, 0, Width + 1, bottomEdge + 1); - _gdi.Line(0, 0, TotalColWidth.Value + 1, 0); - _gdi.Line(0, bottomEdge, TotalColWidth.Value + 1, bottomEdge); - - // Vertical black seperators - for (int i = 0; i < visibleColumns.Count; i++) - { - int pos = visibleColumns[i].Left.Value - _hBar.Value; - _gdi.Line(pos, 0, pos, bottomEdge); - } - - // Draw right most line - if (visibleColumns.Any()) - { - int right = TotalColWidth.Value - _hBar.Value; - _gdi.Line(right, 0, right, bottomEdge); - } - } - - // Emphasis - foreach (var column in visibleColumns.Where(c => c.Emphasis)) - { - _gdi.SetBrush(SystemColors.ActiveBorder); - if (HorizontalOrientation) - { - _gdi.FillRectangle(1, visibleColumns.IndexOf(column) * CellHeight + 1, ColumnWidth - 1, ColumnHeight - 1); - } - else - { - _gdi.FillRectangle(column.Left.Value + 1 - _hBar.Value, 1, column.Width.Value - 1, ColumnHeight - 1); - } - } - - // If the user is hovering over a column - if (IsHoveringOnColumnCell) - { - if (HorizontalOrientation) - { - for (int i = 0; i < visibleColumns.Count; i++) - { - if (visibleColumns[i] != CurrentCell.Column) - { - continue; - } - - if (CurrentCell.Column.Emphasis) - { - _gdi.SetBrush(Add(SystemColors.Highlight, 0x00222222)); - } - else - { - _gdi.SetBrush(SystemColors.Highlight); - } - - _gdi.FillRectangle(1, i * CellHeight + 1, ColumnWidth - 1, ColumnHeight - 1); - } - } - else - { - // TODO multiple selected columns - for (int i = 0; i < visibleColumns.Count; i++) - { - if (visibleColumns[i] == CurrentCell.Column) - { - // Left of column is to the right of the viewable area or right of column is to the left of the viewable area - if (visibleColumns[i].Left.Value - _hBar.Value > Width || visibleColumns[i].Right.Value - _hBar.Value < 0) - { - continue; - } - - int left = visibleColumns[i].Left.Value - _hBar.Value; - int width = visibleColumns[i].Right.Value - _hBar.Value - left; - - if (CurrentCell.Column.Emphasis) - { - _gdi.SetBrush(Add(SystemColors.Highlight, 0x00550000)); - } - else - { - _gdi.SetBrush(SystemColors.Highlight); - } - - _gdi.FillRectangle(left + 1, 1, width - 1, ColumnHeight - 1); - } - } - } - } + if (Renderer == RollRenderer.GDIPlus) + GDIP_DrawColumnBg(e, visibleColumns); + else if (Renderer == RollRenderer.GDI) + GDI_DrawColumnBg(visibleColumns); } // TODO refactor this and DoBackGroundCallback functions. @@ -388,57 +67,30 @@ namespace BizHawk.Client.EmuHawk /// private void DrawBg(PaintEventArgs e, List visibleColumns) { - if (UseCustomBackground && QueryItemBkColor != null) - { - DoBackGroundCallback(e, visibleColumns); - } + if (Renderer == RollRenderer.GDIPlus) + GDIP_DrawBg(e, visibleColumns); + else if (Renderer == RollRenderer.GDI) + GDI_DrawBg(e, visibleColumns); + } - if (GridLines) - { - _gdi.SetSolidPen(SystemColors.ControlLight); - if (HorizontalOrientation) - { - // Columns - for (int i = 1; i < VisibleRows + 1; i++) - { - int x = RowsToPixels(i); - _gdi.Line(x, 1, x, DrawHeight); - } + /// + /// Given a cell with rowindex inbetween 0 and VisibleRows, it draws the background color specified. Do not call with absolute rowindices. + /// + private void DrawCellBG(PaintEventArgs e, Color color, Cell cell, List visibleColumns) + { + if (Renderer == RollRenderer.GDIPlus) + GDIP_DrawCellBG(e, color, cell, visibleColumns); + else if (Renderer == RollRenderer.GDI) + GDI_DrawCellBG(color, cell, visibleColumns); + } - // Rows - for (int i = 0; i < visibleColumns.Count + 1; i++) - { - _gdi.Line(RowsToPixels(0) + 1, i * CellHeight - _vBar.Value, DrawWidth, i * CellHeight - _vBar.Value); - } - } - else - { - // Columns - int y = ColumnHeight + 1; - int? totalColWidth = TotalColWidth; - foreach (var column in visibleColumns) - { - int x = column.Left.Value - _hBar.Value; - _gdi.Line(x, y, x, Height - 1); - } + #endregion - if (visibleColumns.Any()) - { - _gdi.Line(totalColWidth.Value - _hBar.Value, y, totalColWidth.Value - _hBar.Value, Height - 1); - } + #region Non-Renderer-Specific Methods - // Rows - for (int i = 1; i < VisibleRows + 1; i++) - { - _gdi.Line(0, RowsToPixels(i), Width + 1, RowsToPixels(i)); - } - } - } - - if (_selectedItems.Any()) - { - DoSelectionBG(e, visibleColumns); - } + protected override void OnPaintBackground(PaintEventArgs pevent) + { + // Do nothing, and this should never be called } private void DoSelectionBG(PaintEventArgs e, List visibleColumns) @@ -484,49 +136,10 @@ namespace BizHawk.Client.EmuHawk cellColor = Color.FromArgb(cellColor.R - (int)((cellColor.R - SystemColors.Highlight.R) * alpha), cellColor.G - (int)((cellColor.G - SystemColors.Highlight.G) * alpha), cellColor.B - (int)((cellColor.B - SystemColors.Highlight.B) * alpha)); - DrawCellBG(cellColor, relativeCell, visibleColumns); + DrawCellBG(e, cellColor, relativeCell, visibleColumns); } } - /// - /// Given a cell with rowindex inbetween 0 and VisibleRows, it draws the background color specified. Do not call with absolute rowindices. - /// - private void DrawCellBG(Color color, Cell cell, List visibleColumns) - { - int x, y, w, h; - - if (HorizontalOrientation) - { - x = RowsToPixels(cell.RowIndex.Value) + 1; - w = CellWidth - 1; - y = (CellHeight * visibleColumns.IndexOf(cell.Column)) + 1 - _vBar.Value; // We can't draw without row and column, so assume they exist and fail catastrophically if they don't - h = CellHeight - 1; - if (x < ColumnWidth) - { - return; - } - } - else - { - w = cell.Column.Width.Value - 1; - x = cell.Column.Left.Value - _hBar.Value + 1; - y = RowsToPixels(cell.RowIndex.Value) + 1; // We can't draw without row and column, so assume they exist and fail catastrophically if they don't - h = CellHeight - 1; - if (y < ColumnHeight) - { - return; - } - } - - if (x > DrawWidth || y > DrawHeight) - { - return; - } // Don't draw if off screen. - - _gdi.SetBrush(color); - _gdi.FillRectangle(x, y, w, h); - } - /// /// Calls QueryItemBkColor callback for all visible cells and fills in the background of those cells. /// @@ -546,7 +159,7 @@ namespace BizHawk.Client.EmuHawk for (int i = 0, f = 0; f < range; i++, f++) { f += _lagFrames[i]; - + Color rowColor = Color.White; QueryRowBkColor?.Invoke(f + startIndex, ref rowColor); @@ -573,7 +186,7 @@ namespace BizHawk.Client.EmuHawk Column = visibleColumns[j], RowIndex = i }; - DrawCellBG(itemColor, cell, visibleColumns); + DrawCellBG(e, itemColor, cell, visibleColumns); } } } @@ -583,7 +196,7 @@ namespace BizHawk.Client.EmuHawk for (int i = 0, f = 0; f < range; i++, f++) // Vertical { f += _lagFrames[i]; - + Color rowColor = Color.White; QueryRowBkColor?.Invoke(f + startIndex, ref rowColor); @@ -610,11 +223,13 @@ namespace BizHawk.Client.EmuHawk Column = visibleColumns[j], RowIndex = i }; - DrawCellBG(itemColor, cell, visibleColumns); + DrawCellBG(e, itemColor, cell, visibleColumns); } } } } } + + #endregion } } diff --git a/BizHawk.Client.EmuHawk/CustomControls/InputRoll.cs b/BizHawk.Client.EmuHawk/CustomControls/InputRoll.cs index 5bab6dc2e7..98455379bd 100644 --- a/BizHawk.Client.EmuHawk/CustomControls/InputRoll.cs +++ b/BizHawk.Client.EmuHawk/CustomControls/InputRoll.cs @@ -15,7 +15,10 @@ namespace BizHawk.Client.EmuHawk // Row width is specified for horizontal orientation public partial class InputRoll : Control { - private readonly GDIRenderer _gdi; + private RollRenderer Renderer = RollRenderer.GDIPlus; + + private Font _commonFont; + private readonly SortedSet _selectedItems = new SortedSet(new SortCell()); private readonly VScrollBar _vBar; @@ -24,8 +27,6 @@ namespace BizHawk.Client.EmuHawk private readonly Timer _hoverTimer = new Timer(); private readonly byte[] _lagFrames = new byte[256]; // Large enough value that it shouldn't ever need resizing. // apparently not large enough for 4K - private readonly IntPtr _rotatedFont; - private readonly IntPtr _normalFont; private readonly Color _foreColor; private readonly Color _backColor; @@ -35,7 +36,6 @@ namespace BizHawk.Client.EmuHawk private int _maxCharactersInHorizontal = 1; private int _rowCount; - private Size _charSize; private RollColumn _columnDown; @@ -52,6 +52,9 @@ namespace BizHawk.Client.EmuHawk public InputRoll() { + // set renderer once at InputRoll instantiation + Renderer = (RollRenderer)TAStudio.InputRollRenderer; + UseCustomBackground = true; GridLines = true; CellWidthPadding = 3; @@ -59,24 +62,29 @@ namespace BizHawk.Client.EmuHawk CurrentCell = null; ScrollMethod = "near"; - var commonFont = new Font("Arial", 8, FontStyle.Bold); - _normalFont = GDIRenderer.CreateNormalHFont(commonFont, 6); - - // PrepDrawString doesn't actually set the font, so this is rather useless. - // I'm leaving this stuff as-is so it will be a bit easier to fix up with another rendering method. - _rotatedFont = GDIRenderer.CreateRotatedHFont(commonFont, true); - - SetStyle(ControlStyles.AllPaintingInWmPaint, true); - SetStyle(ControlStyles.UserPaint, true); - SetStyle(ControlStyles.SupportsTransparentBackColor, true); - SetStyle(ControlStyles.Opaque, true); - - _gdi = new GDIRenderer(); - - using (var g = CreateGraphics()) - using (_gdi.LockGraphics(g)) + switch (Renderer) { - _charSize = _gdi.MeasureString("A", commonFont); // TODO make this a property so changing it updates other values. + case RollRenderer.GDI: + _commonFont = new Font("Arial", 8, FontStyle.Bold); + _normalFont = GDIRenderer.CreateNormalHFont(_commonFont, 6); + + // PrepDrawString doesn't actually set the font, so this is rather useless. + // I'm leaving this stuff as-is so it will be a bit easier to fix up with another rendering method. + _rotatedFont = GDIRenderer.CreateRotatedHFont(_commonFont, true); + + SetStyle(ControlStyles.AllPaintingInWmPaint, true); + SetStyle(ControlStyles.UserPaint, true); + SetStyle(ControlStyles.SupportsTransparentBackColor, true); + SetStyle(ControlStyles.Opaque, true); + + _gdi = new GDIRenderer(); + GDIConstruction(); + break; + case RollRenderer.GDIPlus: +// _commonFont = new Font("Courier New", 8, FontStyle.Bold); + _commonFont = new Font("Arial", 8, FontStyle.Bold); + GDIPConstruction(); + break; } UpdateCellSize(); @@ -126,11 +134,7 @@ namespace BizHawk.Client.EmuHawk protected override void Dispose(bool disposing) { - _gdi.Dispose(); - - GDIRenderer.DestroyHFont(_normalFont); - GDIRenderer.DestroyHFont(_rotatedFont); - + if (Renderer == RollRenderer.GDI) GDIDispose(); base.Dispose(disposing); } @@ -193,7 +197,7 @@ namespace BizHawk.Client.EmuHawk { return _hBar.SmallChange / CellWidth; } - + if (CellHeight == 0) CellHeight++; return _vBar.SmallChange / CellHeight; } @@ -643,7 +647,7 @@ namespace BizHawk.Client.EmuHawk { return _hBar.Value / CellWidth; } - + if (CellHeight == 0) CellHeight++; return _vBar.Value / CellHeight; } @@ -771,6 +775,7 @@ namespace BizHawk.Client.EmuHawk { get { + if (CellHeight == 0) CellHeight++; if (HorizontalOrientation) { return (DrawWidth - ColumnWidth) / CellWidth; @@ -789,6 +794,7 @@ namespace BizHawk.Client.EmuHawk { get { + if (CellHeight == 0) CellHeight++; if (HorizontalOrientation) { return _vBar.Value / CellHeight; @@ -805,6 +811,7 @@ namespace BizHawk.Client.EmuHawk { get { + if (CellHeight == 0) CellHeight++; List columnList = VisibleColumns.ToList(); int ret; if (HorizontalOrientation) @@ -1009,7 +1016,7 @@ namespace BizHawk.Client.EmuHawk Refresh(); } } - else if (_columnDown != null) // Kind of silly feeling to have this check twice, but the only alternative I can think of has it refreshing twice when pointed column changes with column down, and speed matters + else if (_columnDown != null) // Kind of silly feeling to have this check twice, but the only alternative I can think of has it refreshing twice when pointed column changes with column down, and speed matters { Refresh(); } @@ -1579,10 +1586,13 @@ namespace BizHawk.Client.EmuHawk // See MSDN Page for more information on the dumb ScrollBar.Maximum Property private void RecalculateScrollBars() { + if (_vBar == null || _hBar == null) return; + UpdateDrawSize(); var columns = _columns.VisibleColumns.ToList(); + if (CellHeight == 0) CellHeight++; if (HorizontalOrientation) { NeedsVScrollbar = columns.Count > DrawHeight / CellHeight; @@ -1763,6 +1773,8 @@ namespace BizHawk.Client.EmuHawk { newCell.RowIndex = PixelsToRows(x); + if (CellHeight == 0) CellHeight++; + int colIndex = (y + _vBar.Value) / CellHeight; if (colIndex >= 0 && colIndex < columns.Count) { @@ -1798,7 +1810,15 @@ namespace BizHawk.Client.EmuHawk /// The new width of the RollColumn object. private int UpdateWidth(RollColumn col) { - col.Width = (col.Text.Length * _charSize.Width) + (CellWidthPadding * 4); + switch (Renderer) + { + case RollRenderer.GDI: + col.Width = (col.Text.Length * _charSize.Width) + (CellWidthPadding * 4); + break; + case RollRenderer.GDIPlus: + col.Width = (int)Math.Round((col.Text.Length * _charSizeF.Width) + (CellWidthPadding * 4)); + break; + } return col.Width.Value; } @@ -1863,6 +1883,7 @@ namespace BizHawk.Client.EmuHawk { return (int)Math.Floor((float)(pixels - ColumnWidth) / CellWidth); } + if (CellHeight == 0) CellHeight++; return (int)Math.Floor((float)(pixels - ColumnHeight) / CellHeight); } @@ -1889,8 +1910,17 @@ namespace BizHawk.Client.EmuHawk /// private void UpdateCellSize() { - CellHeight = _charSize.Height + (CellHeightPadding * 2); - CellWidth = (_charSize.Width * MaxCharactersInHorizontal) + (CellWidthPadding * 4); // Double the padding for horizontal because it looks better + switch (Renderer) + { + case RollRenderer.GDI: + CellHeight = _charSize.Height + (CellHeightPadding * 2); + CellWidth = (_charSize.Width * MaxCharactersInHorizontal) + (CellWidthPadding * 4); // Double the padding for horizontal because it looks better + break; + case RollRenderer.GDIPlus: + CellHeight = (int)Math.Round(_charSizeF.Height + (CellHeightPadding * 2) + 1); // needed for GDI+ to match GDI cell height + CellWidth = (int)Math.Round((_charSizeF.Width * MaxCharactersInHorizontal) + (CellWidthPadding * 4)); // Double the padding for horizontal because it looks better + break; + } } // SuuperW: Count lag frames between FirstDisplayed and given display position @@ -2260,6 +2290,12 @@ namespace BizHawk.Client.EmuHawk } } + public enum RollRenderer + { + GDI = 0, + GDIPlus = 1 + } + #endregion } } diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs index 08426ffc74..c104438c8e 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs @@ -125,6 +125,9 @@ namespace BizHawk.Client.EmuHawk this.DefaultStateSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.SettingsSubMenu = new System.Windows.Forms.ToolStripMenuItem(); this.RotateMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.RendererOptionsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.SetRenderer0 = new System.Windows.Forms.ToolStripMenuItem(); + this.SetRenderer1 = new System.Windows.Forms.ToolStripMenuItem(); this.HideLagFramesSubMenu = new System.Windows.Forms.ToolStripMenuItem(); this.HideLagFrames0 = new System.Windows.Forms.ToolStripMenuItem(); this.HideLagFrames1 = new System.Windows.Forms.ToolStripMenuItem(); @@ -964,6 +967,7 @@ namespace BizHawk.Client.EmuHawk // this.SettingsSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.RotateMenuItem, + this.RendererOptionsMenuItem, this.HideLagFramesSubMenu, this.iconsToolStripMenuItem, this.toolStripSeparator23, @@ -982,6 +986,34 @@ namespace BizHawk.Client.EmuHawk this.RotateMenuItem.Text = "Rotate"; this.RotateMenuItem.Click += new System.EventHandler(this.RotateMenuItem_Click); // + // RendererOptionsMenuItem + // + this.RendererOptionsMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.SetRenderer0, + this.SetRenderer1}); + this.RendererOptionsMenuItem.Name = "RendererOptionsMenuItem"; + this.RendererOptionsMenuItem.Size = new System.Drawing.Size(183, 22); + this.RendererOptionsMenuItem.Text = "Renderer"; + this.RendererOptionsMenuItem.DropDownOpened += new System.EventHandler(this.SelectedRendererSubMenu_DropDownOpened); + // + // SetRenderer0 + // + this.SetRenderer0.CheckOnClick = true; + this.SetRenderer0.Name = "SetRenderer0"; + this.SetRenderer0.Size = new System.Drawing.Size(180, 22); + this.SetRenderer0.Tag = 0; + this.SetRenderer0.Text = "GDI"; + this.SetRenderer0.Click += new System.EventHandler(this.SetRenderer_Click); + // + // SetRenderer1 + // + this.SetRenderer1.CheckOnClick = true; + this.SetRenderer1.Name = "SetRenderer1"; + this.SetRenderer1.Size = new System.Drawing.Size(180, 22); + this.SetRenderer1.Tag = 1; + this.SetRenderer1.Text = "GDI+ (Experimental)"; + this.SetRenderer1.Click += new System.EventHandler(this.SetRenderer_Click); + // // HideLagFramesSubMenu // this.HideLagFramesSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -1816,5 +1848,8 @@ namespace BizHawk.Client.EmuHawk private System.Windows.Forms.ToolStripMenuItem AutoRestoreOnMouseUpOnlyMenuItem; private System.Windows.Forms.ToolStripMenuItem SingleClickFloatEditMenuItem; private System.Windows.Forms.ToolStripMenuItem LoadBranchOnDoubleclickMenuItem; + private System.Windows.Forms.ToolStripMenuItem RendererOptionsMenuItem; + private System.Windows.Forms.ToolStripMenuItem SetRenderer0; + private System.Windows.Forms.ToolStripMenuItem SetRenderer1; } -} \ No newline at end of file +} diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs index f24ce05243..4b62b355ca 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs @@ -9,6 +9,7 @@ using BizHawk.Client.Common; using BizHawk.Client.Common.MovieConversionExtensions; using BizHawk.Client.EmuHawk.ToolExtensions; using BizHawk.Client.EmuHawk.WinFormExtensions; +using BizHawk.Common; namespace BizHawk.Client.EmuHawk { @@ -1115,6 +1116,51 @@ namespace BizHawk.Client.EmuHawk RotateMenuItem.ShortcutKeyDisplayString = TasView.RotateHotkeyStr; } + private void SelectedRendererSubMenu_DropDownOpened(object sender, EventArgs e) + { + if (OSTailoredCode.CurrentOS == OSTailoredCode.DistinctOS.Windows) + { + if (Global.Config.TasStudioRenderer != InputRollRenderer) + { + switch (Global.Config.TasStudioRenderer) + { + case 0: + SetRenderer0.Text = "GDI (Pending TAStudio Restart)"; + SetRenderer0.Enabled = false; + SetRenderer0.Checked = true; + SetRenderer1.Text = "GDI+ (Experimental)"; + SetRenderer1.Checked = false; + break; + case 1: + SetRenderer1.Text = "GDI+ (Pending TAStudio Restart)"; + SetRenderer1.Enabled = false; + SetRenderer1.Checked = true; + SetRenderer0.Text = "GDI"; + SetRenderer0.Checked = false; + break; + } + } + else + { + SetRenderer0.Text = "GDI"; + SetRenderer0.Enabled = true; + SetRenderer1.Text = "GDI+ (Experimental)"; + SetRenderer1.Enabled = true; + SetRenderer0.Checked = Global.Config.TasStudioRenderer == 0; + SetRenderer1.Checked = Global.Config.TasStudioRenderer == 1; + } + } + else + { + SetRenderer0.Checked = false; + SetRenderer0.Enabled = false; + SetRenderer0.Visible = false; + SetRenderer1.Checked = true; + SetRenderer1.Enabled = false; + SetRenderer1.Visible = true; + } + } + private void HideLagFramesSubMenu_DropDownOpened(object sender, EventArgs e) { HideLagFrames0.Checked = TasView.LagFramesToHide == 0; @@ -1163,6 +1209,16 @@ namespace BizHawk.Client.EmuHawk CurrentTasMovie.FlagChanges(); } + private void SetRenderer_Click(object sender, EventArgs e) + { + var incoming = (int)(sender as ToolStripMenuItem).Tag; + if (incoming != InputRollRenderer) + { + MessageBox.Show("Changing the input roll renderer requires a\nmanual restart of TAStudio", "Renderer Change Warning", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + Global.Config.TasStudioRenderer = incoming; + } + private void HideLagFramesX_Click(object sender, EventArgs e) { TasView.LagFramesToHide = (int)((ToolStripMenuItem)sender).Tag; diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 85a08c5ebe..67035181b3 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -15,6 +15,7 @@ using BizHawk.Client.Common.MovieConversionExtensions; using BizHawk.Client.EmuHawk.WinFormExtensions; using BizHawk.Client.EmuHawk.ToolExtensions; +using BizHawk.Common; using BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES; namespace BizHawk.Client.EmuHawk @@ -25,6 +26,13 @@ namespace BizHawk.Client.EmuHawk public TasMovie CurrentTasMovie => Global.MovieSession.Movie as TasMovie; private MainForm Mainform => GlobalWin.MainForm; + /// + /// Static settings so that InputRoll.cs can determine its renderer ahead of instantiation + /// 0: GDI + /// 1: GDI+ + /// + public static int InputRollRenderer = OSTailoredCode.CurrentOS == OSTailoredCode.DistinctOS.Windows ? 0 : 1; + public bool IsInMenuLoop { get; private set; } public string StatesPath => PathManager.MakeAbsolutePath(Global.Config.PathEntries["Global", "TAStudio states"].Path, null); @@ -113,6 +121,10 @@ namespace BizHawk.Client.EmuHawk public TAStudio() { Settings = new TAStudioSettings(); + + // input roll renderer must be set before InputRoll initialisation + InputRollRenderer = OSTailoredCode.CurrentOS == OSTailoredCode.DistinctOS.Windows ? Global.Config.TasStudioRenderer : 1; + InitializeComponent(); InitializeSeekWorker(); diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.resx b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.resx index 30922f753b..34d44a8aa8 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.resx +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.resx @@ -123,19 +123,19 @@ - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAlwSFlzAAALAQAA - CwEBNnBPWwAAAk1JREFUOE+VkktvUlEUhVsfsdHoREcOHJr4C/RPONHoXzBxoOlAJybOlAZtkdSa2piY - NsjjFiiX8qY8ChYL+ECB2lKB8rq0k5bEmiiRbtc+6b2B6MTBR/ZZa+919j3tEBH9RbudHD6E63/2qAwc - treT58BVRVnWl8vBbLEg7wNC/QPaMrwb4GT/jFa024mzQLe56c9GwjM7klXXlcw6ksyPSbLoKByc/lUq - +TbQMwrODARAGAF3SxtexSMbf8vOCVp9ZyK+/euaW9TO+SfksOlprSjvoteAjU5rAYqSuFyvR1PR8Ewv - GJii8rcAoYFSb+d4gDAgNI/8jGTHOFUroT3410QAHuk4Am4Vi/KOzz2JGxfFcLMZI3wK5T7ZqaXEhcYb - WU2PKJM2H7Ra8XE14AQO91dTpk4k9JLq9YgYHghoxcWZPa/bSCH/C2o0orPaBo1GbDQee9VJxF+zoYFP - wtpGWgpN0/uMRWgcyiG1WsSkBhxFwG0E7AV8z2lrKyxuYvgBs2kLr4z1XcLj4SA2gD+nBhxB8p1sxtKZ - t4xR/otTDNdqS1oQw7ezx2/AfxVok1oAmh+WSt7v/MKLLgOtr3tEQD+sseeyPyX0dqHdVAOGq9XQPazX - /JyzH9itY+SQ9LSSnKV8fkHANWvsoYc/JYaZERHAPzicBw9AoZBf+BnwTZEN/4G2N4egZg1eDz05cIHn - tACmUgmeAtdhRsvlwH6x6Dr4+EESoO5B68JLo+eSOjMQwKDpGLgCJtDoBysgBXzQDOBifz8zcPh/aOgP - 7nYTiVA2JaoAAAAASUVORK5CYII= + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6 + JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAAsBAAALAQE2cE9bAAACTUlE + QVQ4T5WSS29SURSFWx+x0ehERw4cmvgL9E840ehfMHGg6UAnJs6UBm2R1JramJg2yOMWKJfypjwKFgv4 + QIHaUoHyurSTlsSaKJFu1z7pvYHoxMFH9llr73X2Pe0QEf1Fu50cPoTrf/aoDBy2t5PnwFVFWdaXy8Fs + sSDvA0L9A9oyvBvgZP+MVrTbibNAt7npz0bCMzuSVdeVzDqSzI9JsugoHJz+VSr5NtAzCs4MBEAYAXdL + G17FIxt/y84JWn1nIr7965pb1M75J+Sw6WmtKO+i14CNTmsBipK4XK9HU9HwTC8YmKLytwChgVJv53iA + MCA0j/yMZMc4VSuhPfjXRAAe6TgCbhWL8o7PPYkbF8VwsxkjfArlPtmppcSFxhtZTY8okzYftFrxcTXg + BA73V1OmTiT0kur1iBgeCGjFxZk9r9tIIf8LajSis9oGjUZsNB571UnEX7OhgU/C2kZaCk3T+4xFaBzK + IbVaxKQGHEXAbQTsBXzPaWsrLG5i+AGzaQuvjPVdwuPhIDaAP6cGHEHynWzG0pm3jFH+i1MM12pLWhDD + t7PHb8B/FWiTWgCaH5ZK3u/8wosuA62ve0RAP6yx57I/JfR2od1UA4ar1dA9rNf8nLMf2K1j5JD0tJKc + pXx+QcA1a+yhhz8lhpkREcA/OJwHD0ChkF/4GfBNkQ3/gbY3h6BmDV4PPTlwgee0AKZSCZ4C12FGy+XA + frHoOvj4QRKg7kHrwkuj55I6MxDAoOkYuAIm0OgHKyAFfNAM4GJ/PzNw+H9o6A/udhOJUDYlqgAAAABJ + RU5ErkJggg==