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 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 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 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 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 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 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 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, rect.Width, 0); _renderer.Line(0, bottomEdge, rect.Width, 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; if (right <= rect.Left + rect.Width) { _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 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 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 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 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); } } } } } }