2019-10-26 21:49:52 +00:00
using System ;
using System.Collections.Generic ;
using System.ComponentModel ;
using System.Drawing ;
using System.Linq ;
using System.Windows.Forms ;
using BizHawk.Client.Common ;
using BizHawk.Client.EmuHawk.CustomControls ;
namespace BizHawk.Client.EmuHawk
{
// Row width depends on font size and padding
// Column width is specified in column headers
// Row width is specified for horizontal orientation
public partial class InputRoll : Control
{
private readonly IControlRenderer _renderer ;
private readonly SortedSet < Cell > _selectedItems = new SortedSet < Cell > ( new SortCell ( ) ) ;
// scrollbar location(s) are calculated later (e.g. on resize)
private readonly VScrollBar _vBar = new VScrollBar { Visible = false } ;
private readonly HScrollBar _hBar = new HScrollBar { Visible = false } ;
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 Color _foreColor ;
private readonly Color _backColor ;
private RollColumns _columns = new RollColumns ( ) ;
private bool _horizontalOrientation ;
private bool _programmaticallyUpdatingScrollBarValues ;
private int _rowCount ;
2019-12-03 00:38:24 +00:00
private SizeF _charSize ;
2019-11-04 01:55:38 +00:00
// Updated on paint
2019-10-28 02:59:42 +00:00
private int [ ] _horizontalColumnHeights ;
private int [ ] _horizontalColumnTops ;
2019-10-26 21:49:52 +00:00
private RollColumn _columnDown ;
private RollColumn _columnResizing ;
private int? _currentX ;
private int? _currentY ;
2019-11-26 21:55:07 +00:00
private Cell _lastCell ; // The previous cell the mouse was in
2019-11-26 21:37:17 +00:00
private int _drawHeight ;
private int _drawWidth ;
2019-10-26 21:49:52 +00:00
// Hiding lag frames (Mainly intended for < 60fps play.)
2019-10-26 23:35:12 +00:00
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
2019-10-26 21:49:52 +00:00
public int LagFramesToHide { get ; set ; }
2019-10-26 23:35:12 +00:00
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
2019-10-26 21:49:52 +00:00
public bool HideWasLagFrames { get ; set ; }
2019-11-26 16:09:24 +00:00
/// <summary>
/// Gets or sets a value indicating whether or not the control will respond to right-click events with a context menu
/// </summary>
2019-10-26 23:35:12 +00:00
[Category("Behavior")]
2019-11-26 15:42:14 +00:00
public bool AllowRightClickSelection { get ; set ; } = true ;
2019-10-26 23:35:12 +00:00
2019-11-26 16:09:24 +00:00
/// <summary>
/// Gets or sets a value indicating whether or not Home and End will navigate to the beginning or end of the list
/// </summary>
[Category("Behavior")]
public bool AllowMassNavigationShortcuts { get ; set ; } = true ;
2019-10-26 23:35:12 +00:00
[Category("Behavior")]
2019-10-26 21:49:52 +00:00
public bool LetKeysModifySelection { get ; set ; }
2019-10-26 23:35:12 +00:00
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
2019-10-26 21:49:52 +00:00
public bool SuspendHotkeys { get ; set ; }
public InputRoll ( )
{
SetStyle ( ControlStyles . AllPaintingInWmPaint , true ) ;
SetStyle ( ControlStyles . UserPaint , true ) ;
SetStyle ( ControlStyles . SupportsTransparentBackColor , true ) ;
SetStyle ( ControlStyles . Opaque , true ) ;
SetStyle ( ControlStyles . OptimizedDoubleBuffer , true ) ;
2019-11-24 20:38:05 +00:00
_renderer = new GdiPlusRenderer ( Font ) ;
2019-10-26 21:49:52 +00:00
UpdateCellSize ( ) ;
_vBar . SmallChange = CellHeight ;
_vBar . LargeChange = CellHeight * 20 ;
_hBar . SmallChange = CellWidth ;
_hBar . LargeChange = 20 ;
Controls . Add ( _vBar ) ;
Controls . Add ( _hBar ) ;
_vBar . ValueChanged + = VerticalBar_ValueChanged ;
_hBar . ValueChanged + = HorizontalBar_ValueChanged ;
RecalculateScrollBars ( ) ;
_columns . ChangedCallback = ColumnChangedCallback ;
_hoverTimer . Interval = 750 ;
_hoverTimer . Tick + = HoverTimerEventProcessor ;
_hoverTimer . Stop ( ) ;
_foreColor = ForeColor ;
_backColor = BackColor ;
}
private void HoverTimerEventProcessor ( object sender , EventArgs e )
{
_hoverTimer . Stop ( ) ;
2019-11-26 21:55:07 +00:00
CellHovered ? . Invoke ( this , new CellEventArgs ( _lastCell , CurrentCell ) ) ;
2019-10-26 21:49:52 +00:00
}
protected override void Dispose ( bool disposing )
{
_renderer . Dispose ( ) ;
base . Dispose ( disposing ) ;
}
2019-12-04 02:03:10 +00:00
public void ExpandColumnToFitText ( string columnName , string text )
{
var column = AllColumns . SingleOrDefault ( c = > c . Name = = columnName ) ;
if ( column ! = null )
{
using var g = CreateGraphics ( ) ;
2019-12-07 03:56:34 +00:00
using ( _renderer . LockGraphics ( g ) )
2019-12-04 02:03:10 +00:00
{
var strLength = ( int ) _renderer . MeasureString ( text , Font ) . Width + ( CellWidthPadding * 2 ) ;
if ( column . Width < strLength )
{
column . Width = strLength ;
AllColumns . ColumnsChanged ( ) ;
Refresh ( ) ;
}
}
}
}
2019-10-26 21:49:52 +00:00
protected override void OnDoubleClick ( EventArgs e )
{
if ( IsHoveringOnColumnEdge )
{
if ( HorizontalOrientation )
{
// TODO
}
else
{
var maxLength = CurrentCell . Column . Text ? . Length ? ? 0 ;
for ( int i = 0 ; i < RowCount ; i + + )
{
string text = "" ;
int offSetX = 0 , offSetY = 0 ;
QueryItemText ? . Invoke ( i , CurrentCell . Column , out text , ref offSetX , ref offSetY ) ;
if ( text . Length > maxLength )
{
maxLength = text . Length ;
}
}
var newWidth = ( maxLength * _charSize . Width ) + ( CellWidthPadding * 2 ) ;
2019-12-03 00:38:24 +00:00
CurrentCell . Column . Width = ( int ) newWidth ;
2019-10-26 21:49:52 +00:00
_columns . ColumnsChanged ( ) ;
Refresh ( ) ;
}
}
base . OnDoubleClick ( e ) ;
}
#region Properties
/// <summary>
/// Gets or sets the amount of left and right padding on the text inside a cell
/// </summary>
[DefaultValue(3)]
[Category("Behavior")]
public int CellWidthPadding { get ; set ; }
/// <summary>
/// Gets or sets the amount of top and bottom padding on the text inside a cell
/// </summary>
[DefaultValue(1)]
[Category("Behavior")]
public int CellHeightPadding { get ; set ; }
/// <summary>
/// Gets or sets a value indicating whether grid lines are displayed around cells
/// </summary>
[Category("Appearance")]
[DefaultValue(true)]
2019-11-29 19:44:47 +00:00
public bool GridLines { get ; set ; } = true ;
2019-10-26 21:49:52 +00:00
/// <summary>
/// Gets or sets a value indicating whether the control is horizontal or vertical
/// </summary>
[Category("Behavior")]
public bool HorizontalOrientation
{
2019-11-16 17:41:34 +00:00
get = > _horizontalOrientation ;
2019-10-26 21:49:52 +00:00
set
{
if ( _horizontalOrientation ! = value )
{
int temp = ScrollSpeed ;
_horizontalOrientation = value ;
OrientationChanged ( ) ;
_hBar . SmallChange = CellWidth ;
_vBar . SmallChange = CellHeight ;
ScrollSpeed = temp ;
}
}
}
/// <summary>
/// Gets or sets the scrolling speed
/// </summary>
[Category("Behavior")]
public int ScrollSpeed
{
get
{
if ( HorizontalOrientation )
{
return _hBar . SmallChange / CellWidth ;
}
return _vBar . SmallChange / CellHeight ;
}
set
{
if ( HorizontalOrientation )
{
_hBar . SmallChange = value * CellWidth ;
}
else
{
_vBar . SmallChange = value * CellHeight ;
}
}
}
/// <summary>
/// Gets or sets the sets the virtual number of rows to be displayed. Does not include the column header row.
/// </summary>
[Category("Behavior")]
public int RowCount
{
2019-11-16 17:41:34 +00:00
get = > _rowCount ;
2019-10-26 21:49:52 +00:00
set
{
2019-11-26 20:37:49 +00:00
if ( _rowCount ! = value )
{
_rowCount = value ;
_selectedItems . RemoveWhere ( i = > i . RowIndex > = _rowCount ) ;
2019-12-02 00:08:22 +00:00
RecalculateScrollBars ( ) ;
2019-11-26 20:37:49 +00:00
}
// Similarly to ListView in virtual mode, we want to always refresh
// when setting row count, that gives the calling code assurance that
// redraw will happen
2019-12-07 02:58:19 +00:00
Redraw ( ) ;
}
}
2019-12-07 02:27:15 +00:00
2019-12-07 02:58:19 +00:00
public void Redraw ( )
{
if ( HorizontalOrientation )
{
2019-12-07 15:27:56 +00:00
int x = MaxColumnWidth ;
int y = 0 ;
int w = Width - x ;
int h = VisibleColumns . Any ( )
? GetHColBottom ( VisibleColumns . Count ( ) - 1 )
: 0 ;
2019-12-07 15:55:02 +00:00
h = Math . Min ( h , _drawHeight ) ;
2019-12-07 15:27:56 +00:00
Invalidate ( new Rectangle ( x , y , w , h ) ) ;
2019-12-07 02:58:19 +00:00
}
else
{
2019-12-07 04:23:23 +00:00
int x = 0 ;
2019-12-07 02:58:19 +00:00
int y = ColumnHeight + 1 ;
2019-12-07 02:27:15 +00:00
2019-12-07 02:58:19 +00:00
int w = VisibleColumns . Any ( )
? Math . Min ( VisibleColumns . Max ( c = > c . Right ) - _hBar . Value , Width )
: 0 ;
2019-12-07 04:23:23 +00:00
int h = Math . Min ( RowCount * CellHeight , Height - y ) ;
2019-12-07 02:58:19 +00:00
Invalidate ( new Rectangle ( x , y , w , h ) ) ;
2019-10-26 21:49:52 +00:00
}
}
/// <summary>
/// Gets or sets a value indicating whether columns can be resized
/// </summary>
[Category("Behavior")]
public bool AllowColumnResize { get ; set ; }
/// <summary>
/// Gets or sets a value indicating whether columns can be reordered
/// </summary>
[Category("Behavior")]
public bool AllowColumnReorder { get ; set ; }
/// <summary>
/// Gets or sets a value indicating whether the entire row will always be selected
/// </summary>
[Category("Appearance")]
[DefaultValue(false)]
public bool FullRowSelect { get ; set ; }
/// <summary>
/// Gets or sets a value indicating whether multiple items can to be selected
/// </summary>
[Category("Behavior")]
[DefaultValue(true)]
public bool MultiSelect { get ; set ; }
/// <summary>
/// Gets or sets a value indicating whether the control is in input painting mode
/// </summary>
[Category("Behavior")]
[DefaultValue(false)]
public bool InputPaintingMode { get ; set ; }
/// <summary>
/// All visible columns
/// </summary>
[Category("Behavior")]
public IEnumerable < RollColumn > VisibleColumns = > _columns . VisibleColumns ;
/// <summary>
/// Gets or sets how the InputRoll scrolls when calling ScrollToIndex.
/// </summary>
[DefaultValue("near")]
[Category("Behavior")]
2019-11-26 18:53:10 +00:00
public string ScrollMethod { get ; set ; } = "near" ;
2019-10-26 21:49:52 +00:00
/// <summary>
2019-10-26 22:16:15 +00:00
/// Gets or sets a value indicating how the scrolling behavior for the hover event
2019-10-26 21:49:52 +00:00
/// </summary>
[Category("Behavior")]
public bool AlwaysScroll { get ; set ; }
/// <summary>
/// Gets or sets the lowest seek interval to activate the progress bar
/// </summary>
[Category("Behavior")]
public int SeekingCutoffInterval { get ; set ; }
/// <summary>
/// Returns all columns including those that are not visible
/// </summary>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public RollColumns AllColumns = > _columns ;
[DefaultValue(750)]
[Category("Behavior")]
public int HoverInterval
{
2019-11-16 17:41:34 +00:00
get = > _hoverTimer . Interval ;
set = > _hoverTimer . Interval = value ;
2019-10-26 21:49:52 +00:00
}
2019-12-03 02:29:29 +00:00
/// <summary>
/// Gets or sets a value indicating whether or not the control can be toggled into HorizontalOrientation mode
/// </summary>
[DefaultValue(false)]
[Category("Behavior")]
public bool Rotatable { get ; set ; }
2019-10-26 21:49:52 +00:00
#endregion
#region Event Handlers
/// <summary>
/// Fire the <see cref="QueryItemText"/> event which requests the text for the passed cell
/// </summary>
[Category("Virtual")]
public event QueryItemTextHandler QueryItemText ;
/// <summary>
/// Fire the <see cref="QueryItemBkColor"/> event which requests the background color for the passed cell
/// </summary>
[Category("Virtual")]
public event QueryItemBkColorHandler QueryItemBkColor ;
[Category("Virtual")]
public event QueryRowBkColorHandler QueryRowBkColor ;
/// <summary>
/// Fire the <see cref="QueryItemIconHandler"/> event which requests an icon for a given cell
/// </summary>
[Category("Virtual")]
public event QueryItemIconHandler QueryItemIcon ;
/// <summary>
/// Fire the QueryFrameLag event which checks if a given frame is a lag frame
/// </summary>
[Category("Virtual")]
public event QueryFrameLagHandler QueryFrameLag ;
/// <summary>
/// Fires when the mouse moves from one cell to another (including column header cells)
/// </summary>
[Category("Mouse")]
public event CellChangeEventHandler PointedCellChanged ;
/// <summary>
/// Fires when a cell is hovered on
/// </summary>
[Category("Mouse")]
public event HoverEventHandler CellHovered ;
/// <summary>
/// Occurs when a column header is clicked
/// </summary>
[Category("Action")]
public event ColumnClickEventHandler ColumnClick ;
/// <summary>
/// Occurs when a column header is right-clicked
/// </summary>
[Category("Action")]
public event ColumnClickEventHandler ColumnRightClick ;
/// <summary>
/// Occurs whenever the 'SelectedItems' property for this control changes
/// </summary>
[Category("Behavior")]
public event EventHandler SelectedIndexChanged ;
/// <summary>
/// Occurs whenever the mouse wheel is scrolled while the right mouse button is held
/// </summary>
[Category("Behavior")]
public event RightMouseScrollEventHandler RightMouseScrolled ;
[Category("Property Changed")]
[Description("Occurs when the column header has been reordered")]
public event ColumnReorderedEventHandler ColumnReordered ;
[Category("Action")]
[Description("Occurs when the scroll value of the visible rows change (in vertical orientation this is the vertical scroll bar change, and in horizontal it is the horizontal scroll bar)")]
public event RowScrollEvent RowScroll ;
[Category("Action")]
[Description("Occurs when the scroll value of the columns (in vertical orientation this is the horizontal scroll bar change, and in horizontal it is the vertical scroll bar)")]
public event ColumnScrollEvent ColumnScroll ;
[Category("Action")]
[Description("Occurs when a cell is dragged and then dropped into a new cell, old cell is the cell that was being dragged, new cell is its new destination")]
public event CellDroppedEvent CellDropped ;
/// <summary>
/// Retrieve the text for a cell
/// </summary>
public delegate void QueryItemTextHandler ( int index , RollColumn column , out string text , ref int offsetX , ref int offsetY ) ;
/// <summary>
/// Retrieve the background color for a cell
/// </summary>
public delegate void QueryItemBkColorHandler ( int index , RollColumn column , ref Color color ) ;
public delegate void QueryRowBkColorHandler ( int index , ref Color color ) ;
/// <summary>
/// Retrieve the image for a given cell
/// </summary>
public delegate void QueryItemIconHandler ( int index , RollColumn column , ref Bitmap icon , ref int offsetX , ref int offsetY ) ;
/// <summary>
/// Check if a given frame is a lag frame
/// </summary>
public delegate bool QueryFrameLagHandler ( int index , bool hideWasLag ) ;
public delegate void CellChangeEventHandler ( object sender , CellEventArgs e ) ;
public delegate void HoverEventHandler ( object sender , CellEventArgs e ) ;
public delegate void RightMouseScrollEventHandler ( object sender , MouseEventArgs e ) ;
public delegate void ColumnClickEventHandler ( object sender , ColumnClickEventArgs e ) ;
public delegate void ColumnReorderedEventHandler ( object sender , ColumnReorderedEventArgs e ) ;
public delegate void RowScrollEvent ( object sender , EventArgs e ) ;
public delegate void ColumnScrollEvent ( object sender , EventArgs e ) ;
public delegate void CellDroppedEvent ( object sender , CellEventArgs e ) ;
public class CellEventArgs
{
public CellEventArgs ( Cell oldCell , Cell newCell )
{
OldCell = oldCell ;
NewCell = newCell ;
}
2019-10-26 22:16:15 +00:00
public Cell OldCell { get ; }
public Cell NewCell { get ; }
2019-10-26 21:49:52 +00:00
}
public class ColumnClickEventArgs
{
public ColumnClickEventArgs ( RollColumn column )
{
Column = column ;
}
2019-10-26 22:16:15 +00:00
public RollColumn Column { get ; }
2019-10-26 21:49:52 +00:00
}
public class ColumnReorderedEventArgs
{
public ColumnReorderedEventArgs ( int oldDisplayIndex , int newDisplayIndex , RollColumn column )
{
Column = column ;
OldDisplayIndex = oldDisplayIndex ;
NewDisplayIndex = newDisplayIndex ;
}
2019-10-26 22:16:15 +00:00
public RollColumn Column { get ; }
public int OldDisplayIndex { get ; }
public int NewDisplayIndex { get ; }
2019-10-26 21:49:52 +00:00
}
#endregion
#region Api
2019-11-29 22:12:23 +00:00
private int? _lastSelectedRow ;
2019-10-26 21:49:52 +00:00
public void SelectRow ( int index , bool val )
{
if ( _columns . VisibleColumns . Any ( ) )
{
if ( val )
{
SelectCell ( new Cell
{
RowIndex = index ,
Column = _columns [ 0 ]
} ) ;
2019-11-29 22:12:23 +00:00
_lastSelectedRow = index ;
2019-10-26 21:49:52 +00:00
}
else
{
IEnumerable < Cell > items = _selectedItems . Where ( cell = > cell . RowIndex = = index ) ;
_selectedItems . RemoveWhere ( items . Contains ) ;
2019-11-29 22:12:23 +00:00
_lastSelectedRow = _selectedItems . LastOrDefault ( ) ? . RowIndex ;
2019-10-26 21:49:52 +00:00
}
}
}
public void SelectAll ( )
{
var oldFullRowVal = FullRowSelect ;
FullRowSelect = true ;
for ( int i = 0 ; i < RowCount ; i + + )
{
SelectRow ( i , true ) ;
}
FullRowSelect = oldFullRowVal ;
2019-11-29 22:12:23 +00:00
_lastSelectedRow = RowCount ;
2019-10-26 21:49:52 +00:00
}
public void DeselectAll ( )
{
2019-11-29 22:12:23 +00:00
_lastSelectedRow = null ;
2019-10-26 21:49:52 +00:00
_selectedItems . Clear ( ) ;
}
public void TruncateSelection ( int index )
{
_selectedItems . RemoveWhere ( cell = > cell . RowIndex > index ) ;
2019-11-29 22:12:23 +00:00
_lastSelectedRow = _selectedItems . LastOrDefault ( ) ? . RowIndex ;
2019-10-26 21:49:52 +00:00
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool IsPointingAtColumnHeader = > IsHoveringOnColumnCell ;
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
2019-11-26 21:55:07 +00:00
public int? FirstSelectedIndex = > AnyRowsSelected
? SelectedRows . Min ( )
: ( int? ) null ;
2019-10-26 21:49:52 +00:00
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
2019-11-26 21:55:07 +00:00
public int? LastSelectedIndex = > AnyRowsSelected
? SelectedRows . Max ( )
: ( int? ) null ;
2019-10-26 21:49:52 +00:00
/// <summary>
/// Gets or sets the current Cell that the mouse was in.
/// </summary>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Cell CurrentCell { get ; set ; }
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool IsPaintDown { get ; private set ; }
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool RightButtonHeld { get ; private set ; }
public string UserSettingsSerialized ( )
{
var settings = ConfigService . SaveWithType ( Settings ) ;
return settings ;
}
public void LoadSettingsSerialized ( string settingsJson )
{
var settings = ConfigService . LoadWithType ( settingsJson ) ;
// TODO: don't silently fail, inform the user somehow
2019-11-26 15:48:26 +00:00
if ( settings is InputRollSettings rollSettings )
2019-10-26 21:49:52 +00:00
{
_columns = rollSettings . Columns ;
_columns . ChangedCallback = ColumnChangedCallback ;
HorizontalOrientation = rollSettings . HorizontalOrientation ;
LagFramesToHide = rollSettings . LagFramesToHide ;
HideWasLagFrames = rollSettings . HideWasLagFrames ;
}
}
private InputRollSettings Settings = > new InputRollSettings
{
Columns = _columns ,
HorizontalOrientation = HorizontalOrientation ,
LagFramesToHide = LagFramesToHide ,
HideWasLagFrames = HideWasLagFrames
} ;
public class InputRollSettings
{
public RollColumns Columns { get ; set ; }
public bool HorizontalOrientation { get ; set ; }
public int LagFramesToHide { get ; set ; }
public bool HideWasLagFrames { get ; set ; }
}
/// <summary>
/// Gets or sets the first visible row index, if scrolling is needed
/// </summary>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int FirstVisibleRow
{
2019-10-26 22:16:15 +00:00
get
2019-10-26 21:49:52 +00:00
{
if ( HorizontalOrientation )
{
return _hBar . Value / CellWidth ;
}
return _vBar . Value / CellHeight ;
}
set
{
if ( HorizontalOrientation )
{
if ( NeedsHScrollbar )
{
_programmaticallyUpdatingScrollBarValues = true ;
if ( value * CellWidth < = _hBar . Maximum )
{
_hBar . Value = value * CellWidth ;
}
else
{
_hBar . Value = _hBar . Maximum ;
}
_programmaticallyUpdatingScrollBarValues = false ;
}
}
else
{
if ( NeedsVScrollbar )
{
_programmaticallyUpdatingScrollBarValues = true ;
if ( value * CellHeight < = _vBar . Maximum )
{
_vBar . Value = value * CellHeight ;
}
else
{
_vBar . Value = _vBar . Maximum ;
}
_programmaticallyUpdatingScrollBarValues = false ;
}
}
2019-11-26 17:19:34 +00:00
PointMouseToNewCell ( ) ;
2019-10-26 21:49:52 +00:00
}
}
private int LastFullyVisibleRow
{
get
{
int halfRow = 0 ;
2019-11-26 21:37:17 +00:00
if ( ( _drawHeight - ColumnHeight - 3 ) % CellHeight < CellHeight / 2 )
2019-10-26 21:49:52 +00:00
{
halfRow = 1 ;
}
return FirstVisibleRow + VisibleRows - halfRow + CountLagFramesDisplay ( VisibleRows - halfRow ) ;
}
}
2019-11-26 21:55:07 +00:00
private int LastVisibleRow
2019-10-26 21:49:52 +00:00
{
2019-11-16 17:41:34 +00:00
get = > FirstVisibleRow + VisibleRows + CountLagFramesDisplay ( VisibleRows ) ;
2019-10-26 21:49:52 +00:00
set
{
int halfRow = 0 ;
2019-11-26 21:37:17 +00:00
if ( ( _drawHeight - ColumnHeight - 3 ) % CellHeight < CellHeight / 2 )
2019-10-26 21:49:52 +00:00
{
halfRow = 1 ;
}
if ( LagFramesToHide = = 0 )
{
FirstVisibleRow = Math . Max ( value - ( VisibleRows - halfRow ) , 0 ) ;
}
else
{
if ( Math . Abs ( LastFullyVisibleRow - value ) > VisibleRows ) // Big jump
{
FirstVisibleRow = Math . Max ( value - ( ExpectedDisplayRange ( ) - halfRow ) , 0 ) ;
SetLagFramesArray ( ) ;
}
// Small jump, more accurate
int lastVisible = LastFullyVisibleRow ;
do
{
if ( ( lastVisible - value ) / ( LagFramesToHide + 1 ) ! = 0 )
{
FirstVisibleRow = Math . Max ( FirstVisibleRow - ( ( lastVisible - value ) / ( LagFramesToHide + 1 ) ) , 0 ) ;
}
else
{
FirstVisibleRow - = Math . Sign ( lastVisible - value ) ;
}
SetLagFramesArray ( ) ;
lastVisible = LastFullyVisibleRow ;
}
while ( ( lastVisible - value < 0 | | lastVisible - value > _lagFrames [ VisibleRows - halfRow ] ) & & FirstVisibleRow ! = 0 ) ;
}
2019-11-26 17:19:34 +00:00
PointMouseToNewCell ( ) ;
2019-10-26 21:49:52 +00:00
}
}
2019-11-26 21:55:07 +00:00
private bool IsVisible ( int index )
2019-10-26 21:49:52 +00:00
{
2019-11-26 15:48:26 +00:00
return index > = FirstVisibleRow & & index < = LastFullyVisibleRow ;
2019-10-26 21:49:52 +00:00
}
public bool IsPartiallyVisible ( int index )
{
return index > = FirstVisibleRow & & index < = LastVisibleRow ;
}
/// <summary>
/// Gets the number of rows currently visible including partially visible rows.
/// </summary>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int VisibleRows
{
get
{
if ( HorizontalOrientation )
{
2019-12-04 01:10:18 +00:00
return ( _drawWidth - MaxColumnWidth ) / CellWidth ;
2019-10-26 21:49:52 +00:00
}
2019-11-26 21:37:17 +00:00
return ( _drawHeight - ColumnHeight - 3 ) / CellHeight ; // Minus three makes it work
2019-10-26 21:49:52 +00:00
}
}
private Cell _draggingCell ;
public void DragCurrentCell ( )
{
_draggingCell = CurrentCell ;
}
public void ReleaseCurrentCell ( )
{
if ( _draggingCell ! = null )
{
var draggedCell = _draggingCell ;
_draggingCell = null ;
if ( CurrentCell ! = draggedCell )
{
CellDropped ? . Invoke ( this , new CellEventArgs ( draggedCell , CurrentCell ) ) ;
}
}
}
/// <summary>
/// Scrolls to the given index, according to the scroll settings.
/// </summary>
public void ScrollToIndex ( int index )
{
if ( ScrollMethod = = "near" )
{
MakeIndexVisible ( index ) ;
}
if ( ! IsVisible ( index ) | | AlwaysScroll )
{
if ( ScrollMethod = = "top" )
{
FirstVisibleRow = index ;
}
else if ( ScrollMethod = = "bottom" )
{
LastVisibleRow = index ;
}
else if ( ScrollMethod = = "center" )
{
if ( LagFramesToHide = = 0 )
{
FirstVisibleRow = Math . Max ( index - ( VisibleRows / 2 ) , 0 ) ;
}
else
{
if ( Math . Abs ( FirstVisibleRow + CountLagFramesDisplay ( VisibleRows / 2 ) - index ) > VisibleRows ) // Big jump
{
FirstVisibleRow = Math . Max ( index - ( ExpectedDisplayRange ( ) / 2 ) , 0 ) ;
SetLagFramesArray ( ) ;
}
// Small jump, more accurate
int lastVisible = FirstVisibleRow + CountLagFramesDisplay ( VisibleRows / 2 ) ;
do
{
if ( ( lastVisible - index ) / ( LagFramesToHide + 1 ) ! = 0 )
{
FirstVisibleRow = Math . Max ( FirstVisibleRow - ( ( lastVisible - index ) / ( LagFramesToHide + 1 ) ) , 0 ) ;
}
else
{
FirstVisibleRow - = Math . Sign ( lastVisible - index ) ;
}
SetLagFramesArray ( ) ;
lastVisible = FirstVisibleRow + CountLagFramesDisplay ( VisibleRows / 2 ) ;
}
while ( ( lastVisible - index < 0 | | lastVisible - index > _lagFrames [ VisibleRows ] ) & & FirstVisibleRow ! = 0 ) ;
}
}
}
2019-11-26 17:19:34 +00:00
PointMouseToNewCell ( ) ;
2019-10-26 21:49:52 +00:00
}
2019-11-28 02:35:05 +00:00
private bool _programmaticallyChangingRow = false ;
2019-10-26 21:49:52 +00:00
/// <summary>
/// Scrolls so that the given index is visible, if it isn't already; doesn't use scroll settings.
/// </summary>
public void MakeIndexVisible ( int index )
{
if ( ! IsVisible ( index ) )
{
2019-11-28 02:35:05 +00:00
_programmaticallyChangingRow = true ;
2019-10-26 21:49:52 +00:00
if ( FirstVisibleRow > index )
{
FirstVisibleRow = index ;
}
else
{
LastVisibleRow = index ;
}
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
2019-11-26 15:48:26 +00:00
public IEnumerable < int > SelectedRows = > _selectedItems
. Where ( cell = > cell . RowIndex . HasValue )
. Select ( cell = > cell . RowIndex . Value )
. Distinct ( ) ;
2019-10-26 21:49:52 +00:00
2019-11-26 15:48:26 +00:00
public bool AnyRowsSelected = > _selectedItems . Any ( cell = > cell . RowIndex . HasValue ) ;
2019-10-26 21:49:52 +00:00
public IEnumerable < ToolStripItem > GenerateContextMenuItems ( )
{
2019-12-03 02:29:29 +00:00
if ( Rotatable )
2019-10-26 21:49:52 +00:00
{
2019-12-03 02:29:29 +00:00
yield return new ToolStripSeparator ( ) ;
2019-10-26 21:49:52 +00:00
2019-12-03 02:29:29 +00:00
var rotate = new ToolStripMenuItem
{
Name = "RotateMenuItem" ,
Text = "Rotate" ,
ShortcutKeyDisplayString = RotateHotkeyStr ,
} ;
rotate . Click + = ( o , ev ) = > { HorizontalOrientation ^ = true ; } ;
2019-10-26 21:49:52 +00:00
2019-12-03 02:29:29 +00:00
yield return rotate ;
}
2019-10-26 21:49:52 +00:00
}
public string RotateHotkeyStr = > "Ctrl+Shift+F" ;
#endregion
#region Mouse and Key Events
private bool _columnDownMoved ;
2019-10-26 22:16:15 +00:00
private int _previousX ; // TODO: move me
2019-10-26 21:49:52 +00:00
2019-11-26 17:19:34 +00:00
// It's necessary to call this anytime the control is programmatically scrolled
// Since the mouse may not be pointing to the same cell anymore
private void PointMouseToNewCell ( )
{
if ( _currentX . HasValue & & _currentY . HasValue )
{
var newCell = CalculatePointedCell ( _currentX . Value , _currentY . Value ) ;
if ( CurrentCell ! = newCell )
{
if ( QueryFrameLag ! = null & & newCell . RowIndex . HasValue )
{
newCell . RowIndex + = CountLagFramesDisplay ( newCell . RowIndex . Value ) ;
}
newCell . RowIndex + = FirstVisibleRow ;
if ( newCell . RowIndex < 0 )
{
newCell . RowIndex = 0 ;
}
2019-11-28 02:35:05 +00:00
if ( ! _programmaticallyChangingRow )
{
_programmaticallyChangingRow = false ;
CellChanged ( newCell ) ;
}
2019-11-26 17:19:34 +00:00
}
}
}
2019-10-26 21:49:52 +00:00
protected override void OnMouseMove ( MouseEventArgs e )
{
_previousX = _currentX ? ? 0 ;
_currentX = e . X ;
_currentY = e . Y ;
if ( _columnResizing ! = null )
{
if ( _currentX ! = _previousX )
{
2019-12-03 17:26:21 +00:00
_columnResizing . Width + = _currentX . Value - _previousX ;
2019-12-03 01:58:32 +00:00
if ( _columnResizing . Width < = 0 )
{
_columnResizing . Width = 1 ;
}
2019-10-26 21:49:52 +00:00
_columns . ColumnsChanged ( ) ;
Refresh ( ) ;
}
}
else if ( _columnDown ! = null )
{
_columnDownMoved = true ;
}
Cell newCell = CalculatePointedCell ( _currentX . Value , _currentY . Value ) ;
// SuuperW: Hide lag frames
if ( QueryFrameLag ! = null & & newCell . RowIndex . HasValue )
{
newCell . RowIndex + = CountLagFramesDisplay ( newCell . RowIndex . Value ) ;
}
newCell . RowIndex + = FirstVisibleRow ;
if ( newCell . RowIndex < 0 )
{
newCell . RowIndex = 0 ;
}
if ( ! newCell . Equals ( CurrentCell ) )
{
CellChanged ( newCell ) ;
2019-11-26 15:48:26 +00:00
if ( IsHoveringOnColumnCell
| | ( WasHoveringOnColumnCell & & ! IsHoveringOnColumnCell ) )
2019-10-26 21:49:52 +00:00
{
Refresh ( ) ;
}
else if ( _columnDown ! = null )
{
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
{
Refresh ( ) ;
}
Cursor = IsHoveringOnColumnEdge | | _columnResizing ! = null
? Cursors . VSplit
: Cursors . Default ;
base . OnMouseMove ( e ) ;
}
protected override void OnMouseEnter ( EventArgs e )
{
CurrentCell = new Cell
{
Column = null ,
RowIndex = null
} ;
base . OnMouseEnter ( e ) ;
}
protected override void OnMouseLeave ( EventArgs e )
{
_currentX = null ;
_currentY = null ;
CurrentCell = null ;
IsPaintDown = false ;
_columnResizing = null ;
_hoverTimer . Stop ( ) ;
Refresh ( ) ;
base . OnMouseLeave ( e ) ;
}
// TODO add query callback of whether to select the cell or not
protected override void OnMouseDown ( MouseEventArgs e )
{
if ( e . Button = = MouseButtons . Left )
{
if ( IsHoveringOnColumnEdge )
{
_columnResizing = CurrentCell . Column ;
}
if ( IsHoveringOnColumnCell )
{
_columnDown = CurrentCell . Column ;
}
else if ( InputPaintingMode )
{
IsPaintDown = true ;
}
}
if ( e . Button = = MouseButtons . Right )
{
if ( ! IsHoveringOnColumnCell )
{
RightButtonHeld = true ;
}
}
if ( e . Button = = MouseButtons . Left )
{
if ( IsHoveringOnDataCell )
{
if ( ModifierKeys = = Keys . Alt )
{
// do marker drag here
}
2019-10-27 21:55:53 +00:00
else if ( ModifierKeys = = Keys . Shift & & CurrentCell . Column . Type = = ColumnType . Text )
2019-10-26 21:49:52 +00:00
{
if ( _selectedItems . Any ( ) )
{
if ( FullRowSelect )
{
var selected = _selectedItems . Any ( c = > c . RowIndex . HasValue & & CurrentCell . RowIndex . HasValue & & c . RowIndex = = CurrentCell . RowIndex ) ;
if ( ! selected )
{
var rowIndices = _selectedItems
. Where ( c = > c . RowIndex . HasValue )
. Select ( c = > c . RowIndex ? ? - 1 )
. Where ( c = > c > = 0 ) // Hack to avoid possible Nullable exceptions
. Distinct ( )
. ToList ( ) ;
var firstIndex = rowIndices . Min ( ) ;
var lastIndex = rowIndices . Max ( ) ;
if ( CurrentCell . RowIndex . Value < firstIndex )
{
for ( int i = CurrentCell . RowIndex . Value ; i < firstIndex ; i + + )
{
SelectCell ( new Cell
{
RowIndex = i ,
Column = CurrentCell . Column
} ) ;
}
}
else if ( CurrentCell . RowIndex . Value > lastIndex )
{
for ( int i = lastIndex + 1 ; i < = CurrentCell . RowIndex . Value ; i + + )
{
SelectCell ( new Cell
{
RowIndex = i ,
Column = CurrentCell . Column
} ) ;
}
}
else // Somewhere in between, a scenario that can happen with ctrl-clicking, find the previous and highlight from there
{
var nearest = rowIndices
. Where ( x = > x < CurrentCell . RowIndex . Value )
. Max ( ) ;
for ( int i = nearest + 1 ; i < = CurrentCell . RowIndex . Value ; i + + )
{
SelectCell ( new Cell
{
RowIndex = i ,
Column = CurrentCell . Column
} ) ;
}
}
}
}
else
{
MessageBox . Show ( "Shift click logic for individual cells has not yet implemented" ) ;
}
}
else
{
SelectCell ( CurrentCell ) ;
}
}
2019-10-27 21:55:53 +00:00
else if ( ModifierKeys = = Keys . Control & & CurrentCell . Column . Type = = ColumnType . Text )
2019-10-26 21:49:52 +00:00
{
SelectCell ( CurrentCell , toggle : true ) ;
}
else if ( ModifierKeys ! = Keys . Shift )
{
_selectedItems . Clear ( ) ;
SelectCell ( CurrentCell ) ;
}
Refresh ( ) ;
SelectedIndexChanged ? . Invoke ( this , new EventArgs ( ) ) ;
}
}
2019-10-26 22:16:15 +00:00
if ( AllowRightClickSelection & & e . Button = = MouseButtons . Right )
2019-10-26 21:49:52 +00:00
{
if ( ! IsHoveringOnColumnCell & & CurrentCell ! = null )
{
_currentX = e . X ;
_currentY = e . Y ;
Cell newCell = CalculatePointedCell ( _currentX . Value , _currentY . Value ) ;
newCell . RowIndex + = FirstVisibleRow ;
2019-11-29 21:21:20 +00:00
// If this cell is not currently selected, clear and select
if ( ! _selectedItems . Contains ( newCell ) )
{
_selectedItems . Clear ( ) ;
SelectCell ( CurrentCell ) ;
}
2019-10-26 21:49:52 +00:00
CellChanged ( newCell ) ;
}
}
2019-11-26 15:50:19 +00:00
base . OnMouseDown ( e ) ;
2019-10-26 21:49:52 +00:00
}
protected override void OnMouseUp ( MouseEventArgs e )
{
if ( _columnResizing = = null & & IsHoveringOnColumnCell )
{
if ( _columnDown ! = null & & _columnDownMoved )
{
DoColumnReorder ( ) ;
_columnDown = null ;
Refresh ( ) ;
}
else if ( e . Button = = MouseButtons . Left )
{
2019-10-28 02:59:42 +00:00
ColumnClickEvent ( ColumnAtPixel ( e . X ) ) ;
2019-10-26 21:49:52 +00:00
}
else if ( e . Button = = MouseButtons . Right )
{
2019-10-28 02:59:42 +00:00
ColumnRightClickEvent ( ColumnAtPixel ( e . X ) ) ;
2019-10-26 21:49:52 +00:00
}
}
_columnResizing = null ;
_columnDown = null ;
_columnDownMoved = false ;
RightButtonHeld = false ;
IsPaintDown = false ;
base . OnMouseUp ( e ) ;
}
private void IncrementScrollBar ( ScrollBar bar , bool increment )
{
int newVal ;
if ( increment )
{
newVal = bar . Value + bar . SmallChange ;
if ( newVal > bar . Maximum - bar . LargeChange )
{
newVal = bar . Maximum - bar . LargeChange ;
}
}
else
{
newVal = bar . Value - bar . SmallChange ;
if ( newVal < 0 )
{
newVal = 0 ;
}
}
_programmaticallyUpdatingScrollBarValues = true ;
bar . Value = newVal ;
_programmaticallyUpdatingScrollBarValues = false ;
}
protected override void OnMouseWheel ( MouseEventArgs e )
{
if ( RightButtonHeld )
{
DoRightMouseScroll ( this , e ) ;
}
else
{
if ( HorizontalOrientation )
{
do
{
IncrementScrollBar ( _hBar , e . Delta < 0 ) ;
SetLagFramesFirst ( ) ;
}
while ( _lagFrames [ 0 ] ! = 0 & & _hBar . Value ! = 0 & & _hBar . Value ! = _hBar . Maximum ) ;
}
else
{
do
{
IncrementScrollBar ( _vBar , e . Delta < 0 ) ;
SetLagFramesFirst ( ) ;
}
while ( _lagFrames [ 0 ] ! = 0 & & _vBar . Value ! = 0 & & _vBar . Value ! = _vBar . Maximum ) ;
}
if ( _currentX ! = null )
{
OnMouseMove ( new MouseEventArgs ( MouseButtons . None , 0 , _currentX . Value , _currentY . Value , 0 ) ) ;
}
Refresh ( ) ;
}
}
private void DoRightMouseScroll ( object sender , MouseEventArgs e )
{
RightMouseScrolled ? . Invoke ( sender , e ) ;
}
private void ColumnClickEvent ( RollColumn column )
{
ColumnClick ? . Invoke ( this , new ColumnClickEventArgs ( column ) ) ;
}
private void ColumnRightClickEvent ( RollColumn column )
{
ColumnRightClick ? . Invoke ( this , new ColumnClickEventArgs ( column ) ) ;
}
2019-11-26 18:07:00 +00:00
// This allows arrow keys to be detected by KeyDown.
protected override void OnPreviewKeyDown ( PreviewKeyDownEventArgs e )
{
if ( e . KeyCode = = Keys . Left | | e . KeyCode = = Keys . Right | | e . KeyCode = = Keys . Up | | e . KeyCode = = Keys . Down )
{
e . IsInputKey = true ;
}
}
2019-10-26 21:49:52 +00:00
protected override void OnKeyDown ( KeyEventArgs e )
{
if ( ! SuspendHotkeys )
{
if ( e . Control & & ! e . Alt & & e . Shift & & e . KeyCode = = Keys . F ) // Ctrl+Shift+F
{
2019-12-03 02:29:29 +00:00
if ( Rotatable )
{
HorizontalOrientation ^ = true ;
}
2019-10-26 21:49:52 +00:00
}
// Scroll
else if ( ! e . Control & & ! e . Alt & & ! e . Shift & & e . KeyCode = = Keys . PageUp ) // Page Up
{
2019-11-26 16:28:32 +00:00
var selectedRow = SelectedRows . Any ( ) ? SelectedRows . First ( ) : FirstVisibleRow ;
var increment = LastVisibleRow - FirstVisibleRow ;
var newSelectedRow = selectedRow - increment ;
if ( newSelectedRow < 0 )
2019-10-26 21:49:52 +00:00
{
2019-11-26 16:28:32 +00:00
newSelectedRow = 0 ;
2019-10-26 21:49:52 +00:00
}
2019-11-26 16:28:32 +00:00
FirstVisibleRow = newSelectedRow ;
DeselectAll ( ) ;
SelectRow ( newSelectedRow , true ) ;
Refresh ( ) ;
2019-10-26 21:49:52 +00:00
}
else if ( ! e . Control & & ! e . Alt & & ! e . Shift & & e . KeyCode = = Keys . PageDown ) // Page Down
{
2019-11-26 16:28:32 +00:00
var selectedRow = SelectedRows . Any ( ) ? SelectedRows . First ( ) : FirstVisibleRow ;
var increment = LastVisibleRow - FirstVisibleRow ;
var newSelectedRow = selectedRow + increment ;
if ( newSelectedRow > RowCount - 1 )
2019-10-26 21:49:52 +00:00
{
2019-11-26 16:28:32 +00:00
newSelectedRow = RowCount - 1 ;
2019-10-26 21:49:52 +00:00
}
2019-11-26 16:28:32 +00:00
LastVisibleRow = newSelectedRow ;
DeselectAll ( ) ;
SelectRow ( newSelectedRow , true ) ;
Refresh ( ) ;
2019-10-26 21:49:52 +00:00
}
2019-11-26 16:09:24 +00:00
else if ( AllowMassNavigationShortcuts & & ! e . Control & & ! e . Alt & & ! e . Shift & & e . KeyCode = = Keys . Home ) // Home
2019-10-26 21:49:52 +00:00
{
2019-11-26 16:09:24 +00:00
DeselectAll ( ) ;
SelectRow ( 0 , true ) ;
FirstVisibleRow = 0 ;
Refresh ( ) ;
2019-10-26 21:49:52 +00:00
}
2019-11-26 16:09:24 +00:00
else if ( AllowMassNavigationShortcuts & & ! e . Control & & ! e . Alt & & ! e . Shift & & e . KeyCode = = Keys . End ) // End
2019-10-26 21:49:52 +00:00
{
2019-11-26 16:09:24 +00:00
DeselectAll ( ) ;
SelectRow ( RowCount - 1 , true ) ;
LastVisibleRow = RowCount ;
Refresh ( ) ;
2019-10-26 21:49:52 +00:00
}
else if ( ! e . Control & & ! e . Shift & & ! e . Alt & & e . KeyCode = = Keys . Up ) // Up
{
2019-11-26 17:27:55 +00:00
if ( SelectedRows . Any ( ) )
2019-10-26 21:49:52 +00:00
{
2019-11-26 17:27:55 +00:00
var selectedRow = SelectedRows . First ( ) ;
if ( selectedRow > 0 )
{
var targetSelectedRow = selectedRow - 1 ;
DeselectAll ( ) ;
SelectRow ( targetSelectedRow , true ) ;
ScrollToIndex ( targetSelectedRow ) ;
Refresh ( ) ;
}
2019-10-26 21:49:52 +00:00
}
}
else if ( ! e . Control & & ! e . Shift & & ! e . Alt & & e . KeyCode = = Keys . Down ) // Down
{
2019-11-26 17:27:55 +00:00
if ( SelectedRows . Any ( ) )
2019-10-26 21:49:52 +00:00
{
2019-11-26 17:27:55 +00:00
var selectedRow = SelectedRows . First ( ) ;
if ( selectedRow < RowCount - 1 )
{
var targetSelectedRow = selectedRow + 1 ;
DeselectAll ( ) ;
SelectRow ( targetSelectedRow , true ) ;
ScrollToIndex ( targetSelectedRow ) ;
Refresh ( ) ;
}
2019-10-26 21:49:52 +00:00
}
}
2019-11-29 22:12:23 +00:00
else if ( ! e . Control & & e . Shift & & ! e . Alt & & e . KeyCode = = Keys . Up ) // Shift+Up
{
if ( MultiSelect & & _lastSelectedRow > 0 )
{
if ( _selectedItems . Any ( i = > i . RowIndex = = _lastSelectedRow . Value )
& & _selectedItems . Any ( i = > i . RowIndex = = _lastSelectedRow - 1 ) ) // Unhighlight if already highlighted
{
SelectRow ( _lastSelectedRow . Value , false ) ;
}
else
{
SelectRow ( _lastSelectedRow . Value - 1 , true ) ;
}
Refresh ( ) ;
}
}
else if ( ! e . Control & & e . Shift & & ! e . Alt & & e . KeyCode = = Keys . Down ) // Shift+Down
{
if ( MultiSelect & & _lastSelectedRow < RowCount - 1 )
{
if ( _selectedItems . Any ( i = > i . RowIndex = = _lastSelectedRow . Value )
& & _selectedItems . Any ( i = > i . RowIndex = = _lastSelectedRow + 1 ) ) // Unhighlight if already highlighted
{
var origIndex = _lastSelectedRow . Value ;
SelectRow ( origIndex , false ) ;
// SelectRow assumed the max row should be selected, but in this edge case it isn't
_lastSelectedRow = _selectedItems . FirstOrDefault ( ) ? . RowIndex ;
}
else
{
SelectRow ( _lastSelectedRow . Value + 1 , true ) ;
}
Refresh ( ) ;
}
}
// Selection cursor
2019-10-26 21:49:52 +00:00
else if ( e . Control & & ! e . Shift & & ! e . Alt & & e . KeyCode = = Keys . Up ) // Ctrl + Up
{
if ( SelectedRows . Any ( ) & & LetKeysModifySelection & & SelectedRows . First ( ) > 0 )
{
foreach ( var row in SelectedRows . ToList ( ) ) // clones SelectedRows
{
SelectRow ( row - 1 , true ) ;
SelectRow ( row , false ) ;
}
}
}
else if ( e . Control & & ! e . Shift & & ! e . Alt & & e . KeyCode = = Keys . Down ) // Ctrl + Down
{
if ( SelectedRows . Any ( ) & & LetKeysModifySelection )
{
foreach ( var row in SelectedRows . Reverse ( ) ) // clones SelectedRows
{
SelectRow ( row + 1 , true ) ;
SelectRow ( row , false ) ;
}
}
}
else if ( e . Control & & ! e . Shift & & ! e . Alt & & e . KeyCode = = Keys . Left ) // Ctrl + Left
{
if ( SelectedRows . Any ( ) & & LetKeysModifySelection )
{
SelectRow ( SelectedRows . Last ( ) , false ) ;
}
}
else if ( e . Control & & ! e . Shift & & ! e . Alt & & e . KeyCode = = Keys . Right ) // Ctrl + Right
{
if ( SelectedRows . Any ( ) & & LetKeysModifySelection & & SelectedRows . Last ( ) < _rowCount - 1 )
{
SelectRow ( SelectedRows . Last ( ) + 1 , true ) ;
}
}
else if ( e . Control & & e . Shift & & ! e . Alt & & e . KeyCode = = Keys . Left ) // Ctrl + Shift + Left
{
if ( SelectedRows . Any ( ) & & LetKeysModifySelection & & SelectedRows . First ( ) > 0 )
{
SelectRow ( SelectedRows . First ( ) - 1 , true ) ;
}
}
else if ( e . Control & & e . Shift & & ! e . Alt & & e . KeyCode = = Keys . Right ) // Ctrl + Shift + Right
{
if ( SelectedRows . Any ( ) & & LetKeysModifySelection )
{
SelectRow ( SelectedRows . First ( ) , false ) ;
}
}
else if ( e . Control & & ! e . Shift & & ! e . Alt & & e . KeyCode = = Keys . PageUp ) // Ctrl + Page Up
{
//jump to above marker with selection courser
if ( LetKeysModifySelection )
{
}
}
else if ( e . Control & & ! e . Shift & & ! e . Alt & & e . KeyCode = = Keys . PageDown ) // Ctrl + Page Down
{
//jump to below marker with selection courser
if ( LetKeysModifySelection )
{
}
}
}
base . OnKeyDown ( e ) ;
}
#endregion
#region Change Events
protected override void OnResize ( EventArgs e )
{
RecalculateScrollBars ( ) ;
base . OnResize ( e ) ;
}
private void OrientationChanged ( )
{
// TODO scroll to correct positions
ColumnChangedCallback ( ) ;
Refresh ( ) ;
}
/// <summary>
/// Call this function to change the CurrentCell to newCell
/// </summary>
private void CellChanged ( Cell newCell )
{
2019-11-26 21:55:07 +00:00
_lastCell = CurrentCell ;
2019-10-26 21:49:52 +00:00
CurrentCell = newCell ;
if ( PointedCellChanged ! = null & &
2019-11-26 21:55:07 +00:00
( _lastCell . Column ! = CurrentCell . Column | | _lastCell . RowIndex ! = CurrentCell . RowIndex ) )
2019-10-26 21:49:52 +00:00
{
2019-11-26 21:55:07 +00:00
PointedCellChanged ( this , new CellEventArgs ( _lastCell , CurrentCell ) ) ;
2019-10-26 21:49:52 +00:00
}
if ( CurrentCell ? . Column ! = null & & CurrentCell . RowIndex . HasValue )
{
_hoverTimer . Start ( ) ;
}
else
{
_hoverTimer . Stop ( ) ;
}
}
private void VerticalBar_ValueChanged ( object sender , EventArgs e )
{
if ( ! _programmaticallyUpdatingScrollBarValues )
{
Refresh ( ) ;
}
if ( _horizontalOrientation )
{
ColumnScroll ? . Invoke ( _vBar , e ) ;
}
else
{
RowScroll ? . Invoke ( _vBar , e ) ;
}
}
private void HorizontalBar_ValueChanged ( object sender , EventArgs e )
{
if ( ! _programmaticallyUpdatingScrollBarValues )
{
Refresh ( ) ;
}
if ( _horizontalOrientation )
{
RowScroll ? . Invoke ( _hBar , e ) ;
}
else
{
ColumnScroll ? . Invoke ( _vBar , e ) ;
}
}
private void ColumnChangedCallback ( )
{
RecalculateScrollBars ( ) ;
if ( _columns . VisibleColumns . Any ( ) )
{
2019-12-04 01:10:18 +00:00
MaxColumnWidth = _columns . VisibleColumns . Max ( c = > c . Width ) + CellWidthPadding * 4 ;
2019-10-26 21:49:52 +00:00
}
}
#endregion
#region Helpers
private void DoColumnReorder ( )
{
if ( _columnDown ! = CurrentCell . Column )
{
var oldIndex = _columns . IndexOf ( _columnDown ) ;
var newIndex = _columns . IndexOf ( CurrentCell . Column ) ;
ColumnReordered ? . Invoke ( this , new ColumnReorderedEventArgs ( oldIndex , newIndex , _columnDown ) ) ;
_columns . Remove ( _columnDown ) ;
_columns . Insert ( newIndex , _columnDown ) ;
}
}
// ScrollBar.Maximum = DesiredValue + ScrollBar.LargeChange - 1
// See MSDN Page for more information on the dumb ScrollBar.Maximum Property
private void RecalculateScrollBars ( )
{
UpdateDrawSize ( ) ;
2019-11-04 01:55:38 +00:00
var columns = _columns . VisibleColumns . ToList ( ) ;
2019-10-28 02:59:42 +00:00
int iLastColumn = columns . Count - 1 ;
2019-10-26 21:49:52 +00:00
if ( HorizontalOrientation )
{
2019-11-26 21:37:17 +00:00
NeedsVScrollbar = GetHColBottom ( iLastColumn ) > _drawHeight ;
2019-10-26 21:49:52 +00:00
NeedsHScrollbar = RowCount > 1 ;
}
else
{
2019-11-26 21:55:07 +00:00
NeedsVScrollbar = ColumnHeight + ( RowCount * CellHeight ) > Height ;
2019-12-03 18:29:18 +00:00
NeedsHScrollbar = TotalColWidth - _drawWidth + 1 > 0 ;
2019-10-26 21:49:52 +00:00
}
UpdateDrawSize ( ) ;
if ( VisibleRows > 0 )
{
if ( HorizontalOrientation )
{
2019-10-28 02:59:42 +00:00
_hBar . Maximum = Math . Max ( ( VisibleRows - 1 ) * CellWidth , _hBar . Maximum ) ;
_hBar . LargeChange = ( VisibleRows - 1 ) * CellWidth ;
2019-11-26 21:37:17 +00:00
_vBar . LargeChange = Math . Max ( 0 , _drawHeight / 2 ) ;
2019-10-26 21:49:52 +00:00
}
else
{
_vBar . Maximum = Math . Max ( ( VisibleRows - 1 ) * CellHeight , _vBar . Maximum ) ; // ScrollBar.Maximum is dumb
_vBar . LargeChange = ( VisibleRows - 1 ) * CellHeight ;
// DrawWidth can be negative if the TAStudio window is small enough
// Clamp LargeChange to 0 here to prevent exceptions
2019-11-26 21:37:17 +00:00
_hBar . LargeChange = Math . Max ( 0 , _drawWidth / 2 ) ;
2019-10-26 21:49:52 +00:00
}
}
// Update VBar
if ( NeedsVScrollbar )
{
if ( HorizontalOrientation )
{
2019-11-26 21:37:17 +00:00
_vBar . Maximum = GetHColBottom ( iLastColumn ) - _drawHeight + _vBar . LargeChange ;
2019-10-26 21:49:52 +00:00
if ( _vBar . Maximum < 0 )
{
_vBar . Maximum = 0 ;
}
}
else
{
_vBar . Maximum = RowsToPixels ( RowCount + 1 ) - ( CellHeight * 3 ) + _vBar . LargeChange - 1 ;
if ( _vBar . Maximum < 0 )
{
_vBar . Maximum = 0 ;
}
}
_vBar . Location = new Point ( Width - _vBar . Width , 0 ) ;
_vBar . Height = Height ;
_vBar . Visible = true ;
}
else
{
_vBar . Visible = false ;
_vBar . Value = 0 ;
}
// Update HBar
if ( NeedsHScrollbar )
{
if ( HorizontalOrientation )
{
_hBar . Maximum = RowsToPixels ( RowCount + 1 ) - ( CellHeight * 3 ) + _hBar . LargeChange - 1 ;
}
else
{
2019-12-03 18:29:18 +00:00
_hBar . Maximum = TotalColWidth - _drawWidth + _hBar . LargeChange ;
2019-10-26 21:49:52 +00:00
}
_hBar . Location = new Point ( 0 , Height - _hBar . Height ) ;
_hBar . Width = Width - ( NeedsVScrollbar ? ( _vBar . Width + 1 ) : 0 ) ;
_hBar . Visible = true ;
}
else
{
_hBar . Visible = false ;
_hBar . Value = 0 ;
}
}
private void UpdateDrawSize ( )
{
2019-11-26 21:55:07 +00:00
_drawWidth = NeedsVScrollbar
? Width - _vBar . Width
: Width ;
_drawHeight = NeedsHScrollbar
? Height - _hBar . Height
: Height ;
2019-10-26 21:49:52 +00:00
}
/// <summary>
/// If FullRowSelect is enabled, selects all cells in the row that contains the given cell. Otherwise only given cell is added.
/// </summary>
/// <param name="cell">The cell to select.</param>
2019-10-26 22:16:15 +00:00
/// <param name="toggle">Specifies whether or not to toggle the current state, rather than force the value to true</param>
2019-10-26 21:49:52 +00:00
private void SelectCell ( Cell cell , bool toggle = false )
{
if ( cell . RowIndex . HasValue & & cell . RowIndex < RowCount )
{
if ( ! MultiSelect )
{
_selectedItems . Clear ( ) ;
2019-11-29 22:12:23 +00:00
_lastSelectedRow = null ;
2019-10-26 21:49:52 +00:00
}
if ( FullRowSelect )
{
if ( toggle & & _selectedItems . Any ( x = > x . RowIndex . HasValue & & x . RowIndex = = cell . RowIndex ) )
{
_selectedItems . RemoveWhere ( x = > x . RowIndex . HasValue & & x . RowIndex = = cell . RowIndex ) ;
2019-11-29 22:12:23 +00:00
_lastSelectedRow = _selectedItems . LastOrDefault ( ) ? . RowIndex ;
2019-10-26 21:49:52 +00:00
}
else
{
foreach ( var column in _columns )
{
_selectedItems . Add ( new Cell
{
RowIndex = cell . RowIndex ,
Column = column
} ) ;
2019-11-29 22:12:23 +00:00
_lastSelectedRow = cell . RowIndex ;
2019-10-26 21:49:52 +00:00
}
}
}
else
{
2019-11-29 22:12:23 +00:00
_lastSelectedRow = null ; // TODO: tracking this by cell is a lot more work
2019-10-26 21:49:52 +00:00
if ( toggle & & _selectedItems . Any ( x = > x . RowIndex . HasValue & & x . RowIndex = = cell . RowIndex ) )
{
var item = _selectedItems
. FirstOrDefault ( x = > x . Equals ( cell ) ) ;
if ( item ! = null )
{
_selectedItems . Remove ( item ) ;
}
}
else
{
_selectedItems . Add ( CurrentCell ) ;
}
}
}
}
private bool IsHoveringOnColumnCell = > CurrentCell ? . Column ! = null & & ! CurrentCell . RowIndex . HasValue ;
private bool IsHoveringOnColumnEdge = > AllowColumnResize & & IsHoveringOnColumnCell & & IsPointingOnCellEdge ( _currentX ) ;
private bool IsHoveringOnDataCell = > CurrentCell ? . Column ! = null & & CurrentCell . RowIndex . HasValue ;
2019-11-26 21:55:07 +00:00
private bool WasHoveringOnColumnCell = > _lastCell ? . Column ! = null & & ! _lastCell . RowIndex . HasValue ;
2019-10-26 21:49:52 +00:00
private bool IsPointingOnCellEdge ( int? x )
{
if ( x . HasValue )
{
if ( HorizontalOrientation )
{
return false ; // TODO: support column resize in horizontal orientation
}
foreach ( RollColumn column in _columns . VisibleColumns )
{
if ( column . Left - _hBar . Value + ( column . Width - column . Width / 6 ) < = x . Value & & column . Right - _hBar . Value > = x . Value )
{
return true ;
}
}
}
return false ;
}
/// <summary>
/// Finds the specific cell that contains the (x, y) coordinate.
/// </summary>
/// <remarks>The row number that it returns will be between 0 and VisibleRows, NOT the absolute row number.</remarks>
/// <param name="x">X coordinate point.</param>
/// <param name="y">Y coordinate point.</param>
/// <returns>The cell with row number and RollColumn reference, both of which can be null. </returns>
private Cell CalculatePointedCell ( int x , int y )
{
var newCell = new Cell ( ) ;
var columns = _columns . VisibleColumns . ToList ( ) ;
// If pointing to a column header
if ( columns . Any ( ) )
{
if ( HorizontalOrientation )
{
newCell . RowIndex = PixelsToRows ( x ) ;
2019-10-28 02:59:42 +00:00
newCell . Column = ColumnAtPixel ( y ) ;
2019-10-26 21:49:52 +00:00
}
else
{
newCell . RowIndex = PixelsToRows ( y ) ;
2019-10-28 02:59:42 +00:00
newCell . Column = ColumnAtPixel ( x ) ;
2019-10-26 21:49:52 +00:00
}
}
if ( ! ( IsPaintDown | | RightButtonHeld ) & & newCell . RowIndex < = - 1 ) // -2 if we're entering from the top
{
newCell . RowIndex = null ;
}
return newCell ;
}
// A boolean that indicates if the InputRoll is too large vertically and requires a vertical scrollbar.
private bool NeedsVScrollbar { get ; set ; }
// A boolean that indicates if the InputRoll is too large horizontally and requires a horizontal scrollbar.
private bool NeedsHScrollbar { get ; set ; }
2019-12-03 18:29:18 +00:00
// Gets the total width of all the columns by using the last column's Right property.
private int TotalColWidth = > _columns . VisibleColumns . Any ( )
2019-11-26 15:48:26 +00:00
? _columns . VisibleColumns . Last ( ) . Right
2019-12-03 18:29:18 +00:00
: 0 ;
2019-10-26 21:49:52 +00:00
/// <summary>
2019-10-28 02:59:42 +00:00
/// Returns the RollColumn object at the specified visible pixel coordinate.
2019-10-26 21:49:52 +00:00
/// </summary>
2019-11-26 15:48:26 +00:00
/// <param name="pixel">The pixel coordinate.</param>
2019-10-28 02:59:42 +00:00
/// <returns>RollColumn object that contains the pixel coordinate or null if none exists.</returns>
private RollColumn ColumnAtPixel ( int pixel )
2019-10-26 21:49:52 +00:00
{
2019-10-28 02:59:42 +00:00
if ( _horizontalOrientation )
2019-10-26 21:49:52 +00:00
{
2019-10-28 02:59:42 +00:00
foreach ( var item in _columns . VisibleColumns . Select ( ( n , i ) = > new { Column = n , Index = i } ) )
2019-10-26 21:49:52 +00:00
{
2019-10-28 02:59:42 +00:00
if ( GetHColTop ( item . Index ) - _vBar . Value < = pixel & & GetHColBottom ( item . Index ) - _vBar . Value > = pixel )
{
return item . Column ;
}
}
}
else
2019-11-04 01:55:38 +00:00
{
foreach ( RollColumn column in _columns . VisibleColumns )
{
2019-12-03 18:29:18 +00:00
if ( column . Left - _hBar . Value < = pixel & & column . Right - _hBar . Value > = pixel )
2019-11-04 01:55:38 +00:00
{
return column ;
}
2019-10-26 21:49:52 +00:00
}
}
2019-11-26 15:48:26 +00:00
2019-10-26 21:49:52 +00:00
return null ;
}
/// <summary>
/// Converts a row number to a horizontal or vertical coordinate.
/// </summary>
/// <returns>A vertical coordinate if Vertical Oriented, otherwise a horizontal coordinate.</returns>
private int RowsToPixels ( int index )
{
if ( _horizontalOrientation )
{
2019-12-04 01:10:18 +00:00
return ( index * CellWidth ) + MaxColumnWidth ;
2019-10-26 21:49:52 +00:00
}
return ( index * CellHeight ) + ColumnHeight ;
}
/// <summary>
/// Converts a horizontal or vertical coordinate to a row number.
/// </summary>
/// <param name="pixels">A vertical coordinate if Vertical Oriented, otherwise a horizontal coordinate.</param>
2019-10-26 22:16:15 +00:00
/// <returns>A row number between 0 and VisibleRows if it is a data row, otherwise a negative number if above all Datarows.</returns>
2019-10-26 21:49:52 +00:00
private int PixelsToRows ( int pixels )
{
// Using Math.Floor and float because integer division rounds towards 0 but we want to round down.
if ( _horizontalOrientation )
{
2019-12-04 01:10:18 +00:00
return ( int ) Math . Floor ( ( float ) ( pixels - MaxColumnWidth ) / CellWidth ) ;
2019-10-26 21:49:52 +00:00
}
2019-11-26 15:48:26 +00:00
2019-10-26 21:49:52 +00:00
return ( int ) Math . Floor ( ( float ) ( pixels - ColumnHeight ) / CellHeight ) ;
}
2019-10-28 02:59:42 +00:00
private int GetHColHeight ( int index ) = >
_horizontalColumnHeights ! = null & & index < _horizontalColumnHeights . Length ? _horizontalColumnHeights [ index ] : CellHeight ;
2019-12-04 01:35:19 +00:00
private int GetHColTop ( int index )
{
if ( _horizontalColumnTops = = null )
{
return 0 ;
}
2019-12-07 16:01:30 +00:00
return index > = 0 & & index < _horizontalColumnTops . Length
2019-12-04 01:35:19 +00:00
? _horizontalColumnTops [ index ]
: _horizontalColumnTops . Last ( ) + CellHeight ;
}
2019-11-04 01:55:38 +00:00
private int GetHColBottom ( int index ) = >
GetHColTop ( index ) + GetHColHeight ( index ) ;
// The width of the largest column cell in Horizontal Orientation
2019-12-04 01:10:18 +00:00
private int MaxColumnWidth { get ; set ; }
2019-10-26 21:49:52 +00:00
// The height of a column cell in Vertical Orientation.
2019-12-04 01:11:51 +00:00
private int ColumnHeight = > CellHeight + 2 ;
2019-10-26 21:49:52 +00:00
// The width of a cell in Horizontal Orientation. Only can be changed by changing the Font or CellPadding.
private int CellWidth { get ; set ; }
/// <summary>
/// Gets or sets a value indicating the height of a cell in Vertical Orientation. Only can be changed by changing the Font or CellPadding.
/// </summary>
private int CellHeight { get ; set ; } = 8 ;
/// <summary>
/// Call when _charSize, MaxCharactersInHorizontal, or CellPadding is changed.
/// </summary>
private void UpdateCellSize ( )
{
2019-11-24 21:09:25 +00:00
using ( var g = CreateGraphics ( ) )
2019-12-07 03:56:34 +00:00
using ( _renderer . LockGraphics ( g ) )
2019-11-24 21:09:25 +00:00
{
// Measure width change to ignore extra padding at start/end
var size1 = _renderer . MeasureString ( "A" , Font ) ;
var size2 = _renderer . MeasureString ( "AA" , Font ) ;
2019-12-03 00:38:24 +00:00
_charSize = new SizeF ( size2 . Width - size1 . Width , size1 . Height ) ; // TODO make this a property so changing it updates other values.
2019-11-24 21:09:25 +00:00
}
2019-12-03 00:38:24 +00:00
// TODO: Should we round instead of truncate?
CellHeight = ( int ) _charSize . Height + ( CellHeightPadding * 2 ) ;
2019-12-04 00:57:50 +00:00
CellWidth = ( int ) _charSize . Width + ( CellWidthPadding * 4 ) ; // Double the padding for horizontal because it looks better
2019-12-03 00:38:24 +00:00
2019-12-04 01:10:18 +00:00
if ( _columns . VisibleColumns . Any ( ) )
{
MaxColumnWidth = _columns . VisibleColumns . Max ( c = > c . Width ) + CellWidthPadding * 4 ;
}
2019-11-24 21:09:25 +00:00
}
protected override void OnFontChanged ( EventArgs e )
{
UpdateCellSize ( ) ;
2019-10-26 21:49:52 +00:00
}
// SuuperW: Count lag frames between FirstDisplayed and given display position
private int CountLagFramesDisplay ( int relativeIndex )
{
if ( QueryFrameLag ! = null & & LagFramesToHide ! = 0 )
{
int count = 0 ;
for ( int i = 0 ; i < = relativeIndex ; i + + )
{
count + = _lagFrames [ i ] ;
}
return count ;
}
return 0 ;
}
// Count lag frames between FirstDisplayed and given relative frame index
private int CountLagFramesAbsolute ( int relativeIndex )
{
if ( QueryFrameLag ! = null & & LagFramesToHide ! = 0 )
{
int count = 0 ;
for ( int i = 0 ; i + count < = relativeIndex ; i + + )
{
count + = _lagFrames [ i ] ;
}
return count ;
}
return 0 ;
}
private void SetLagFramesArray ( )
{
2019-12-05 03:17:23 +00:00
int firstVisibleRow = FirstVisibleRow ;
int visibleRows = VisibleRows ;
2019-10-26 21:49:52 +00:00
if ( QueryFrameLag ! = null & & LagFramesToHide ! = 0 )
{
bool showNext = false ;
// First one needs to check BACKWARDS for lag frame count.
SetLagFramesFirst ( ) ;
int f = _lagFrames [ 0 ] ;
2019-12-05 03:17:23 +00:00
if ( QueryFrameLag ( firstVisibleRow + f , HideWasLagFrames ) )
2019-10-26 21:49:52 +00:00
{
showNext = true ;
}
2019-12-05 03:17:23 +00:00
for ( int i = 1 ; i < = visibleRows ; i + + )
2019-10-26 21:49:52 +00:00
{
_lagFrames [ i ] = 0 ;
if ( ! showNext )
{
for ( ; _lagFrames [ i ] < LagFramesToHide ; _lagFrames [ i ] + + )
{
2019-12-05 03:17:23 +00:00
if ( ! QueryFrameLag ( firstVisibleRow + i + f , HideWasLagFrames ) )
2019-10-26 21:49:52 +00:00
{
break ;
}
f + + ;
}
}
else
{
2019-12-05 03:17:23 +00:00
if ( ! QueryFrameLag ( firstVisibleRow + i + f , HideWasLagFrames ) )
2019-10-26 21:49:52 +00:00
{
showNext = false ;
}
}
2019-12-05 03:17:23 +00:00
if ( _lagFrames [ i ] = = LagFramesToHide & & QueryFrameLag ( firstVisibleRow + i + f , HideWasLagFrames ) )
2019-10-26 21:49:52 +00:00
{
showNext = true ;
}
}
}
else
{
2019-12-05 03:17:23 +00:00
for ( int i = 0 ; i < = visibleRows ; i + + )
2019-10-26 21:49:52 +00:00
{
_lagFrames [ i ] = 0 ;
}
}
}
private void SetLagFramesFirst ( )
{
2019-12-05 03:17:23 +00:00
int firstVisibleRow = FirstVisibleRow ;
2019-10-26 21:49:52 +00:00
if ( QueryFrameLag ! = null & & LagFramesToHide ! = 0 )
{
// Count how many lag frames are above displayed area.
int count = 0 ;
do
{
count + + ;
}
2019-12-05 03:17:23 +00:00
while ( QueryFrameLag ( firstVisibleRow - count , HideWasLagFrames ) & & count < = LagFramesToHide ) ;
2019-10-26 21:49:52 +00:00
count - - ;
// Count forward
int fCount = - 1 ;
do
{
fCount + + ;
}
2019-12-05 03:17:23 +00:00
while ( QueryFrameLag ( firstVisibleRow + fCount , HideWasLagFrames ) & & count + fCount < LagFramesToHide ) ;
2019-10-26 21:49:52 +00:00
_lagFrames [ 0 ] = ( byte ) fCount ;
}
else
{
_lagFrames [ 0 ] = 0 ;
}
}
// Number of displayed + hidden frames, if fps is as expected
private int ExpectedDisplayRange ( )
{
return ( VisibleRows + 1 ) * LagFramesToHide ;
}
#endregion
}
}