BizHawk/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.Drawing.cs

646 lines
18 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using BizHawk.Client.EmuHawk.WinFormExtensions;
namespace BizHawk.Client.EmuHawk
{
public partial class InputRoll
{
protected override void OnPaint(PaintEventArgs e)
{
using (_renderer.LockGraphics(e.Graphics))
{
// White Background
_renderer.SetBrush(Color.White);
_renderer.SetSolidPen(Color.White);
_renderer.FillRectangle(e.ClipRectangle);
// Lag frame calculations
SetLagFramesArray();
List<RollColumn> visibleColumns;
if (HorizontalOrientation)
{
CalculateHorizontalColumnPositions(VisibleColumns.ToList());
visibleColumns = VisibleColumns
.Take(_horizontalColumnTops.Count(c => c < e.ClipRectangle.Height))
.ToList();
}
else
{
visibleColumns = _columns.VisibleColumns
.Where(c => c.Right > _hBar.Value)
.Where(c => c.Left - _hBar.Value < e.ClipRectangle.Width)
.ToList();
}
var firstVisibleRow = Math.Max(FirstVisibleRow, 0);
var visibleRows = HorizontalOrientation
? e.ClipRectangle.Width / CellWidth
: e.ClipRectangle.Height / CellHeight;
var lastVisibleRow = firstVisibleRow + visibleRows;
var needsColumnRedraw = HorizontalOrientation || e.ClipRectangle.Y < ColumnHeight;
if (visibleColumns.Any() && needsColumnRedraw)
{
DrawColumnBg(visibleColumns, e.ClipRectangle);
DrawColumnText(visibleColumns);
}
// Background
DrawBg(visibleColumns, e.ClipRectangle, firstVisibleRow, lastVisibleRow);
// Foreground
DrawData(visibleColumns, firstVisibleRow, lastVisibleRow);
DrawColumnDrag(visibleColumns);
DrawCellDrag(visibleColumns);
}
}
private void DrawString(string text, Rectangle rect)
{
if (!string.IsNullOrWhiteSpace(text))
{
_renderer.DrawString(text, rect);
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// Do nothing, and this should never be called
}
private void CalculateHorizontalColumnPositions(List<RollColumn> visibleColumns)
{
int firstVisibleRow = FirstVisibleRow;
if (!HorizontalOrientation)
{
_horizontalColumnHeights = null;
_horizontalColumnTops = null;
return;
}
_horizontalColumnHeights = new int[visibleColumns.Count];
_horizontalColumnTops = new int[visibleColumns.Count];
int top = 0;
int startRow = firstVisibleRow;
for (int j = 0; j < visibleColumns.Count; j++)
{
RollColumn col = visibleColumns[j];
int height = CellHeight;
if (col.Rotatable && col.RotatedHeight != null)
{
height = Math.Max(height, col.RotatedHeight.Value);
}
else if (col.Rotatable)
{
int strOffsetX = 0;
int strOffsetY = 0;
QueryItemText(startRow, col, out var text, ref strOffsetX, ref strOffsetY);
int textWidth = (int)_renderer.MeasureString(text, Font).Width;
height = Math.Max(height, textWidth + (CellWidthPadding * 2));
}
_horizontalColumnHeights[j] = height;
_horizontalColumnTops[j] = top;
top += height;
}
}
private void DrawColumnDrag(List<RollColumn> visibleColumns)
{
if (!(_columnDown?.Width > 0)
|| !_columnDownMoved
|| !_currentX.HasValue
|| !_currentY.HasValue
|| !IsHoveringOnColumnCell)
{
return;
}
int columnWidth = _columnDown.Width;
int columnHeight = CellHeight;
if (HorizontalOrientation)
{
int columnIndex = visibleColumns.IndexOf(_columnDown);
columnWidth = MaxColumnWidth;
columnHeight = GetHColHeight(columnIndex);
}
int x1 = _currentX.Value - (columnWidth / 2);
int y1 = _currentY.Value - (columnHeight / 2);
int textOffsetY = CellHeightPadding;
if (HorizontalOrientation)
{
int textHeight = (int)_renderer.MeasureString(_columnDown.Text, Font).Height;
textOffsetY = (columnHeight - textHeight) / 2;
}
_renderer.SetSolidPen(_backColor);
_renderer.DrawRectangle(new Rectangle(x1, y1, columnWidth, columnHeight));
_renderer.PrepDrawString(Font, _foreColor);
_renderer.DrawString(_columnDown.Text, new Rectangle(x1 + CellWidthPadding, y1 + textOffsetY, columnWidth, columnHeight));
}
private void DrawCellDrag(List<RollColumn> visibleColumns)
{
if (_draggingCell?.RowIndex != null
&& _draggingCell.Column.Width > 0
&& _currentX.HasValue
&& _currentY.HasValue)
{
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 columnHeight = CellHeight;
if (HorizontalOrientation)
{
int columnIndex = visibleColumns.IndexOf(_draggingCell.Column);
columnHeight = GetHColHeight(columnIndex);
}
int x1 = _currentX.Value - (_draggingCell.Column.Width / 2);
int y1 = _currentY.Value - (columnHeight / 2);
int x2 = x1 + _draggingCell.Column.Width;
int y2 = y1 + columnHeight;
_renderer.SetBrush(bgColor);
_renderer.FillRectangle(new Rectangle(x1, y1, x2 - x1, y2 - y1));
_renderer.PrepDrawString(Font, _foreColor);
_renderer.DrawString(text, new Rectangle(x1 + CellWidthPadding + offsetX, y1 + CellHeightPadding + offsetY, x2 - x1, y2 - y1));
}
}
private void DrawColumnText(List<RollColumn> visibleColumns)
{
_renderer.PrepDrawString(Font, _foreColor);
int h = ColumnHeight;
int yOffset = HorizontalOrientation ? -_vBar.Value : 0;
for (int j = 0; j < visibleColumns.Count; j++)
{
var column = visibleColumns[j];
var w = column.Width;
int x, y;
if (HorizontalOrientation)
{
var columnHeight = GetHColHeight(j);
var textHeight = (int)_renderer.MeasureString(column.Text, Font).Height;
x = CellWidthPadding;
y = yOffset + ((columnHeight - textHeight) / 2);
yOffset += columnHeight;
}
else
{
x = column.Left + 2 * CellWidthPadding - _hBar.Value;
// TODO: fix this CellPadding issue (2 * CellPadding vs just CellPadding)
y = CellHeightPadding;
}
if (IsHoveringOnColumnCell && column == CurrentCell.Column)
{
_renderer.PrepDrawString(Font, SystemColors.HighlightText);
DrawString(column.Text, new Rectangle(x, y, column.Width, h));
_renderer.PrepDrawString(Font, _foreColor);
}
else
{
DrawString(column.Text, new Rectangle(x, y, w, h));
}
}
}
private void DrawData(List<RollColumn> visibleColumns, int firstVisibleRow, int lastVisibleRow)
{
if (QueryItemText == null)
{
return;
}
if (!visibleColumns.Any())
{
return;
}
int startRow = firstVisibleRow;
int range = Math.Min(lastVisibleRow, RowCount - 1) - startRow + 1;
_renderer.PrepDrawString(Font, _foreColor);
if (HorizontalOrientation)
{
for (int j = 0; j < visibleColumns.Count; j++)
{
RollColumn col = visibleColumns[j];
int colHeight = GetHColHeight(j);
for (int i = 0, f = 0; f < range; i++, f++)
{
f += _lagFrames[i];
int baseX = RowsToPixels(i) + (col.Rotatable ? CellWidth : 0);
int baseY = GetHColTop(j) - _vBar.Value;
if (!col.Rotatable)
{
Bitmap image = null;
int bitmapOffsetX = 0;
int bitmapOffsetY = 0;
QueryItemIcon?.Invoke(f + startRow, col, ref image, ref bitmapOffsetX, ref bitmapOffsetY);
if (image != null)
{
int x = baseX + CellWidthPadding + bitmapOffsetX;
int y = baseY + CellHeightPadding + bitmapOffsetY;
_renderer.DrawBitmap(image, new Point(x, y));
}
}
int strOffsetX = 0;
int strOffsetY = 0;
QueryItemText(f + startRow, col, out var text, ref strOffsetX, ref strOffsetY);
int textWidth = (int)_renderer.MeasureString(text, Font).Width;
if (col.Rotatable)
{
// Center Text
int textX = Math.Max(((colHeight - textWidth) / 2), CellWidthPadding) + strOffsetX;
int textY = CellWidthPadding + strOffsetY;
_renderer.PrepDrawString(Font, _foreColor, rotate: true);
DrawString(text, new Rectangle(baseX - textY, baseY + textX, 999, CellHeight));
_renderer.PrepDrawString(Font, _foreColor, rotate: false);
}
else
{
// Center Text
int textX = Math.Max(((CellWidth - textWidth) / 2), CellWidthPadding) + strOffsetX;
int textY = CellHeightPadding + strOffsetY;
DrawString(text, new Rectangle(baseX + textX, baseY + textY, MaxColumnWidth, CellHeight));
}
}
}
}
else
{
int xPadding = CellWidthPadding + 1 - _hBar.Value;
for (int i = 0, f = 0; f < range; i++, f++) // Vertical
{
f += _lagFrames[i];
foreach (var column in visibleColumns)
{
RollColumn col = column;
int strOffsetX = 0;
int strOffsetY = 0;
Point point = new Point(col.Left + xPadding, RowsToPixels(i) + CellHeightPadding);
Bitmap image = null;
int bitmapOffsetX = 0;
int bitmapOffsetY = 0;
QueryItemIcon?.Invoke(f + startRow, column, ref image, ref bitmapOffsetX, ref bitmapOffsetY);
if (image != null)
{
_renderer.DrawBitmap(image, new Point(point.X + bitmapOffsetX, point.Y + bitmapOffsetY + CellHeightPadding));
}
QueryItemText(f + startRow, column, out var text, ref strOffsetX, ref strOffsetY);
bool rePrep = false;
if (_selectedItems.Contains(new Cell { Column = column, RowIndex = f + startRow }))
{
_renderer.PrepDrawString(Font, SystemColors.HighlightText);
rePrep = true;
}
DrawString(text, new Rectangle(point.X + strOffsetX, point.Y + strOffsetY, col.Width, ColumnHeight));
if (rePrep)
{
_renderer.PrepDrawString(Font, _foreColor);
}
}
}
}
}
private void DrawColumnBg(List<RollColumn> visibleColumns, Rectangle rect)
{
_renderer.SetBrush(SystemColors.ControlLight);
_renderer.SetSolidPen(Color.Black);
if (HorizontalOrientation)
{
_renderer.FillRectangle(new Rectangle(0, 0, MaxColumnWidth + 1, rect.Height));
int y = -_vBar.Value;
for (int j = 0; j < visibleColumns.Count; j++)
{
_renderer.Line(1, y, MaxColumnWidth, y);
y += GetHColHeight(j);
}
if (visibleColumns.Any())
{
_renderer.Line(1, y, MaxColumnWidth, y);
}
_renderer.Line(0, 0, 0, y);
_renderer.Line(MaxColumnWidth, 0, MaxColumnWidth, y);
}
else
{
int bottomEdge = RowsToPixels(0);
// Gray column box and black line underneath
_renderer.FillRectangle(new Rectangle(0, 0, rect.Width, bottomEdge + 1));
_renderer.Line(0, 0, TotalColWidth, 0);
_renderer.Line(0, bottomEdge, TotalColWidth, bottomEdge);
// Vertical black separators
foreach (var column in visibleColumns)
{
int pos = column.Left - _hBar.Value;
_renderer.Line(pos, 0, pos, bottomEdge);
}
// Draw right most line
if (visibleColumns.Any())
{
int right = TotalColWidth - _hBar.Value;
_renderer.Line(right, 0, right, bottomEdge);
}
}
// Emphasis
foreach (var column in visibleColumns.Where(c => c.Emphasis))
{
_renderer.SetBrush(SystemColors.ActiveBorder);
if (HorizontalOrientation)
{
int columnIndex = visibleColumns.IndexOf(column);
_renderer.FillRectangle(new Rectangle(1, GetHColTop(columnIndex) + 1, MaxColumnWidth - 1, GetHColHeight(columnIndex) - 1));
}
else
{
_renderer.FillRectangle(new Rectangle(column.Left + 1 - _hBar.Value, 1, column.Width - 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;
}
int top = GetHColTop(i) - _vBar.Value;
int height = GetHColHeight(i);
_renderer.SetBrush(CurrentCell.Column.Emphasis
? SystemColors.Highlight.Add(0x00222222)
: SystemColors.Highlight);
_renderer.FillRectangle(new Rectangle(1, top + 1, MaxColumnWidth - 1, height - 1));
}
}
else
{
// TODO multiple selected columns
foreach (var column in visibleColumns)
{
if (column == 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 (column.Left - _hBar.Value > Width || column.Right - _hBar.Value < 0)
{
continue;
}
int left = column.Left - _hBar.Value;
int width = column.Right - _hBar.Value - left;
_renderer.SetBrush(CurrentCell.Column.Emphasis
? SystemColors.Highlight.Add(0x00550000)
: SystemColors.Highlight);
_renderer.FillRectangle(new Rectangle(left + 1, 1, width - 1, ColumnHeight - 1));
}
}
}
}
}
// TODO refactor this and DoBackGroundCallback functions.
// Draw Gridlines and background colors using QueryItemBkColor.
private void DrawBg(List<RollColumn> visibleColumns, Rectangle rect, int firstVisibleRow, int lastVisibleRow)
{
if (QueryItemBkColor != null)
{
DoBackGroundCallback(visibleColumns, rect, firstVisibleRow, lastVisibleRow);
}
if (GridLines)
{
_renderer.SetSolidPen(SystemColors.ControlLight);
if (HorizontalOrientation)
{
// Columns
for (int i = 1; i < lastVisibleRow - firstVisibleRow + 1; i++)
{
int x = RowsToPixels(i);
_renderer.Line(x, 1, x, rect.Height);
}
// Rows
for (int i = 0; i < visibleColumns.Count + 1; i++)
{
// TODO: MaxColumnWidth shouldn't be necessary
// This also makes too many assumptions, the parameters need to drive what is being drawn
int y = GetHColTop(i) - _vBar.Value;
int x = RowsToPixels(0) + 1;
_renderer.Line(x, y, rect.Width + MaxColumnWidth, y);
}
}
else
{
// Columns
int y = ColumnHeight + 1;
foreach (var column in visibleColumns)
{
int x = column.Left - _hBar.Value;
_renderer.Line(x, y, x, rect.Height - 1);
}
if (visibleColumns.Any())
{
int x = TotalColWidth - _hBar.Value;
_renderer.Line(x, y, x, rect.Height - 1);
}
// Rows
for (int i = 1; i < VisibleRows + 1; i++)
{
_renderer.Line(0, RowsToPixels(i), rect.Width + 1, RowsToPixels(i));
}
}
}
if (_selectedItems.Any())
{
DoSelectionBG(visibleColumns, rect);
}
}
private void DoSelectionBG(List<RollColumn> visibleColumns, Rectangle rect)
{
Color rowColor = Color.White;
int firstVisibleRow = FirstVisibleRow;
int lastVisibleRow = LastVisibleRow;
int lastRow = -1;
foreach (Cell cell in _selectedItems)
{
if (!cell.RowIndex.HasValue || cell.RowIndex > lastVisibleRow || cell.RowIndex < firstVisibleRow || !VisibleColumns.Contains(cell.Column))
{
continue;
}
Cell relativeCell = new Cell
{
RowIndex = cell.RowIndex - firstVisibleRow,
Column = cell.Column,
};
relativeCell.RowIndex -= CountLagFramesAbsolute(relativeCell.RowIndex.Value);
if (QueryRowBkColor != null && lastRow != cell.RowIndex.Value)
{
QueryRowBkColor(cell.RowIndex.Value, ref rowColor);
lastRow = cell.RowIndex.Value;
}
Color cellColor = rowColor;
QueryItemBkColor?.Invoke(cell.RowIndex.Value, cell.Column, ref cellColor);
// Alpha layering for cell before selection
float alpha = (float)cellColor.A / 255;
if (cellColor.A != 255 && cellColor.A != 0)
{
cellColor = Color.FromArgb(rowColor.R - (int)((rowColor.R - cellColor.R) * alpha),
rowColor.G - (int)((rowColor.G - cellColor.G) * alpha),
rowColor.B - (int)((rowColor.B - cellColor.B) * alpha));
}
// Alpha layering for selection
alpha = 0.33f;
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, rect);
}
}
// Given a cell with RowIndex in between 0 and VisibleRows, it draws the background color specified. Do not call with absolute row indices.
private void DrawCellBG(Color color, Cell cell, List<RollColumn> visibleColumns, Rectangle rect)
{
int x, y, w, h;
if (HorizontalOrientation)
{
x = RowsToPixels(cell.RowIndex.Value) + 1;
if (x < MaxColumnWidth)
{
return;
}
int columnIndex = visibleColumns.IndexOf(cell.Column);
w = CellWidth - 1;
y = GetHColTop(columnIndex) - _vBar.Value + 1;
h = GetHColHeight(columnIndex) - 1;
}
else
{
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
if (y < ColumnHeight)
{
return;
}
x = cell.Column.Left - _hBar.Value + 1;
w = cell.Column.Width - 1;
h = CellHeight - 1;
}
_renderer.SetBrush(color);
_renderer.FillRectangle(new Rectangle(x, y, w, h));
}
// Calls QueryItemBkColor callback for all visible cells and fills in the background of those cells.
private void DoBackGroundCallback(List<RollColumn> visibleColumns, Rectangle rect, int firstVisibleRow, int lastVisibleRow)
{
if (!visibleColumns.Any())
{
return;
}
int startIndex = firstVisibleRow;
int range = Math.Min(lastVisibleRow, RowCount - 1) - startIndex + 1;
for (int i = 0, f = 0; f < range; i++, f++)
{
f += _lagFrames[i];
Color rowColor = Color.White;
QueryRowBkColor?.Invoke(f + startIndex, ref rowColor);
foreach (var column in visibleColumns)
{
Color itemColor = Color.White;
QueryItemBkColor?.Invoke(f + startIndex, column, ref itemColor);
if (itemColor == Color.White)
{
itemColor = rowColor;
}
else if (itemColor.A != 255 && itemColor.A != 0)
{
float alpha = (float)itemColor.A / 255;
itemColor = Color.FromArgb(rowColor.R - (int)((rowColor.R - itemColor.R) * alpha),
rowColor.G - (int)((rowColor.G - itemColor.G) * alpha),
rowColor.B - (int)((rowColor.B - itemColor.B) * alpha));
}
if (itemColor != Color.White) // An easy optimization, don't draw unless the user specified something other than the default
{
var cell = new Cell
{
Column = column,
RowIndex = i
};
DrawCellBG(itemColor, cell, visibleColumns, rect);
}
}
}
}
}
}