UI/HomeScreen.AllSoftware.cs
using Sandbox.UI;
namespace sGBA;
public partial class HomeScreen
{
private Panel AllSoftwareScroll { get; set; }
private Panel AllSoftwareScrollbar { get; set; }
private Panel AllSoftwareScrollbarThumb { get; set; }
private float _allSoftwareDesiredScrollY;
private float _lastAllSoftwareScrollY = -1f;
private int _allSoftwareTitleDirection = 1;
private bool _pendingAllSoftwareScrollApply;
private bool _allSoftwareSmoothScrollActive;
private bool _allSoftwareScrollbarDragging;
private float _allSoftwareScrollbarDragOffsetY;
private const int AllSoftwareColumns = 6;
private const float AllSoftwareCardWidth = 260f;
private const float AllSoftwareCardHeight = 260f;
private const float AllSoftwareColumnGap = 20f;
private const float AllSoftwareRowGap = 20f;
private const float AllSoftwareGridVerticalPadding = 54f;
private const float AllSoftwareAddButtonHeight = 86f;
private const float AllSoftwareAddButtonTopGap = 96f;
private const float AllSoftwareAddButtonBottomGap = 72f;
private const float AllSoftwareScrollViewportHeight = 906f;
private const float AllSoftwareSelectionTopMargin = 16f;
private const float AllSoftwareSelectionBottomMargin = 58f;
private const float AllSoftwareSmoothScrollRate = 18f;
private const float AllSoftwareScrollbarHeight = 906f;
private readonly record struct AllSoftwareEntry( GameEntry Game, int Index, int Row, int Column, string Key );
private bool ShowAllSoftwareSelection => ShowHomeSelection;
private bool IsAllSoftwareAddSelected => ShowAllSoftwareSelection && _allSoftwareOpen && _input.UseGamepad && _allSoftwareSelection == AllSoftwareAddIndex;
private int AllSoftwareAddIndex => _allGames.Count;
private int AllSoftwareEntryCount => _allGames.Count;
private int AllSoftwareGameRows => _allGames.Count == 0 ? 0 : (int)MathF.Ceiling( _allGames.Count / (float)AllSoftwareColumns );
private int AllSoftwareAddRow => AllSoftwareGameRows;
private float AllSoftwareRowStride => AllSoftwareCardHeight + AllSoftwareRowGap;
private float AllSoftwareGridWidth => AllSoftwareColumns * AllSoftwareCardWidth + Math.Max( 0, AllSoftwareColumns - 1 ) * AllSoftwareColumnGap;
private float AllSoftwareGameContentHeight => AllSoftwareGameRows == 0 ? 0f : AllSoftwareGameRows * AllSoftwareCardHeight + Math.Max( 0, AllSoftwareGameRows - 1 ) * AllSoftwareRowGap;
private float AllSoftwareAddButtonTop => AllSoftwareGridVerticalPadding + AllSoftwareGameContentHeight + AllSoftwareAddButtonTopGap;
private float AllSoftwareContentHeight => AllSoftwareAddButtonTop + AllSoftwareAddButtonHeight + AllSoftwareAddButtonBottomGap;
private float AllSoftwareMaxScrollY => MathF.Max( 0f, AllSoftwareContentHeight - AllSoftwareScrollViewportHeight );
private float AllSoftwareScrollScale => MathF.Max( 0.001f, AllSoftwareScroll?.ScaleToScreen ?? Panel?.ScaleToScreen ?? 1f );
private float AllSoftwareScrollY => AllSoftwareScroll == null
? _allSoftwareDesiredScrollY.Clamp( 0f, AllSoftwareMaxScrollY )
: (AllSoftwareScroll.ScrollOffset.y / AllSoftwareScrollScale).Clamp( 0f, AllSoftwareMaxScrollY );
private bool AllSoftwareShowsScrollbar => AllSoftwareContentHeight > AllSoftwareScrollViewportHeight;
private float AllSoftwareScrollbarThumbHeight => !AllSoftwareShowsScrollbar ? AllSoftwareScrollbarHeight : MathF.Max( 72f, AllSoftwareScrollbarHeight * (AllSoftwareScrollViewportHeight / AllSoftwareContentHeight) );
private float AllSoftwareScrollbarThumbTravel => MathF.Max( 0f, AllSoftwareScrollbarHeight - AllSoftwareScrollbarThumbHeight );
private float AllSoftwareScrollbarThumbTop => AllSoftwareScrollbarThumbTravel <= 0f ? 0f : AllSoftwareScrollbarThumbTravel * (AllSoftwareScrollY / MathF.Max( 1f, AllSoftwareMaxScrollY ));
private string AllSoftwareScrollbarThumbStyle
{
get
{
if ( !AllSoftwareShowsScrollbar )
return "";
return $"top: {AllSoftwareScrollbarThumbTop}px; height: {AllSoftwareScrollbarThumbHeight}px;";
}
}
private string AllSoftwareGridStyle => $"width: {AllSoftwareGridWidth}px; height: {AllSoftwareContentHeight}px;";
private string AllSoftwareGridItemStyle( int row, int column )
{
float left = column * (AllSoftwareCardWidth + AllSoftwareColumnGap);
float top = AllSoftwareGridVerticalPadding + row * AllSoftwareRowStride;
return $"left: {left}px; top: {top}px;";
}
private string AllSoftwareAddRowStyle => $"left: 0px; top: {AllSoftwareAddButtonTop}px; width: {AllSoftwareGridWidth}px;";
private IEnumerable<AllSoftwareEntry> AllSoftwareGridItems
{
get
{
for ( int index = 0; index < AllSoftwareEntryCount; index++ )
{
int row = index / AllSoftwareColumns;
int column = index % AllSoftwareColumns;
yield return new AllSoftwareEntry( _allGames[index], index, row, column, _allGames[index].Path );
}
}
}
private IEnumerable<AllSoftwareEntry> AllSoftwareVisibleItems
{
get
{
int firstRow = Math.Max( 0, (int)MathF.Floor( (AllSoftwareScrollY - AllSoftwareGridVerticalPadding) / AllSoftwareRowStride ) - 1 );
int visibleRows = Math.Max( 1, (int)MathF.Ceiling( AllSoftwareScrollViewportHeight / AllSoftwareRowStride ) + 2 );
int first = firstRow * AllSoftwareColumns;
int last = Math.Min( AllSoftwareEntryCount, first + AllSoftwareColumns * visibleRows );
for ( int index = first; index < last; index++ )
{
int row = index / AllSoftwareColumns;
int column = index % AllSoftwareColumns;
yield return new AllSoftwareEntry( _allGames[index], index, row, column, _allGames[index].Path );
}
}
}
private void OpenAllSoftware()
{
var selectedGame = SelectedGame;
_allSoftwareOpen = true;
_navFocused = false;
int allSoftwareIndex = selectedGame == null ? -1 : _allGames.IndexOf( selectedGame );
_allSoftwareSelection = allSoftwareIndex >= 0 ? allSoftwareIndex : _allGames.Count == 0 ? AllSoftwareAddIndex : 0;
EnsureAllSoftwareSelectionVisible();
Sound.Play( "ui.button.press" );
QueueArtworkRefresh();
StateHasChanged();
}
private void CloseAllSoftware()
{
_allSoftwareOpen = false;
_selectedIndex = HomeGameCount > 0 ? 0 : HomeViewMoreIndex;
_navFocused = false;
_homeCarouselSettleRemaining = 0f;
_queuedHomeLaunchGame = null;
SyncBackdropTexture();
QueueArtworkRefresh();
Sound.Play( "ui.popup.message.close" );
StateHasChanged();
}
private void ActivateAllSoftwareItem( int index )
{
SelectAllSoftwareItem( index, _input.UseGamepad );
if ( index == AllSoftwareAddIndex )
{
ActivateAllSoftwareAdd();
return;
}
var game = index >= 0 && index < _allGames.Count ? _allGames[index] : null;
if ( game != null )
LaunchGame( game );
}
private void ActivateAllSoftwareItemWithMouse( int index )
{
SetAllSoftwareTitleDirectionForMouse( index );
if ( _allSoftwareSelection != index )
{
SelectAllSoftwareItem( index, useGamepad: false );
return;
}
_input.ForceMouseMode();
var game = index >= 0 && index < _allGames.Count ? _allGames[index] : null;
if ( game != null )
LaunchGame( game );
}
private void SetAllSoftwareTitleDirectionForMouse( int index )
{
if ( index < 0 || index >= _allGames.Count )
return;
int row = index / AllSoftwareColumns;
float itemCenterY = AllSoftwareGridVerticalPadding + row * AllSoftwareRowStride + AllSoftwareCardHeight * 0.5f - AllSoftwareScrollY;
_allSoftwareTitleDirection = itemCenterY >= AllSoftwareScrollViewportHeight * 0.5f ? 1 : -1;
}
private void ActivateAllSoftwareAdd()
{
if ( _input.UseGamepad )
SelectAllSoftwareItem( AllSoftwareAddIndex, useGamepad: true );
else
UseMouseInAllSoftware();
OpenAddGamesModal( _input.UseGamepad );
}
private void SelectAllSoftwareItem( int index, bool useGamepad )
{
var next = index.Clamp( 0, AllSoftwareAddIndex );
if ( _allSoftwareSelection == next )
{
if ( !useGamepad )
UseMouseInAllSoftware();
return;
}
_allSoftwareSelection = next;
if ( useGamepad )
SetGamepadMode();
else
_input.ForceMouseMode();
EnsureAllSoftwareSelectionVisible( smooth: true );
Sound.Play( "ui.button.over" );
QueueArtworkRefresh();
StateHasChanged();
}
private void NavigateAllSoftwareHorizontal( int direction )
{
if ( _allSoftwareSelection < 0 )
{
SelectFirstVisibleAllSoftwareItem( useGamepad: true );
return;
}
int next = _allSoftwareSelection + direction;
if ( next < 0 || next > AllSoftwareAddIndex )
return;
if ( _allSoftwareSelection == AllSoftwareAddIndex )
{
if ( direction < 0 && _allGames.Count > 0 )
SelectAllSoftwareItem( _allGames.Count - 1, useGamepad: true );
return;
}
if ( next == AllSoftwareAddIndex )
{
SelectAllSoftwareItem( AllSoftwareAddIndex, useGamepad: true );
return;
}
int currentRow = _allSoftwareSelection / AllSoftwareColumns;
int nextRow = next / AllSoftwareColumns;
if ( currentRow != nextRow )
return;
SelectAllSoftwareItem( next, useGamepad: true );
}
private void NavigateAllSoftwareVertical( int direction )
{
_allSoftwareTitleDirection = Math.Sign( direction );
if ( _allSoftwareSelection < 0 )
{
SelectFirstVisibleAllSoftwareItem( useGamepad: true );
return;
}
if ( direction > 0 && _allSoftwareSelection >= Math.Max( 0, _allGames.Count - AllSoftwareColumns ) )
{
SelectAllSoftwareItem( AllSoftwareAddIndex, useGamepad: true );
return;
}
if ( direction < 0 && _allSoftwareSelection == AllSoftwareAddIndex )
{
int rowStart = Math.Max( 0, (AllSoftwareGameRows - 1) * AllSoftwareColumns );
int column = AllSoftwareColumns / 2;
SelectAllSoftwareItem( Math.Min( Math.Max( 0, _allGames.Count - 1 ), rowStart + column ), useGamepad: true );
return;
}
int next = _allSoftwareSelection + direction * AllSoftwareColumns;
if ( next < 0 || next >= _allGames.Count )
return;
SelectAllSoftwareItem( next, useGamepad: true );
}
private void EnsureAllSoftwareSelectionVisible( bool smooth = false )
{
if ( _allSoftwareSelection < 0 )
{
return;
}
bool isAddSelected = IsAllSoftwareAddSelected;
int row = isAddSelected ? AllSoftwareAddRow : _allSoftwareSelection / AllSoftwareColumns;
float itemBaseTop = isAddSelected ? AllSoftwareAddButtonTop : AllSoftwareGridVerticalPadding + row * AllSoftwareRowStride;
float itemHeight = isAddSelected ? AllSoftwareAddButtonHeight : AllSoftwareCardHeight;
float itemTop = !isAddSelected && row == 0 ? 0f : MathF.Max( 0f, itemBaseTop - AllSoftwareSelectionTopMargin );
float itemBottom = itemBaseTop + itemHeight + AllSoftwareSelectionBottomMargin;
float viewTop = AllSoftwareScrollY;
float viewBottom = viewTop + AllSoftwareScrollViewportHeight;
float nextScroll = viewTop;
if ( isAddSelected )
nextScroll = AllSoftwareMaxScrollY;
else if ( itemTop < viewTop )
nextScroll = itemTop;
else if ( itemBottom > viewBottom )
nextScroll = itemBottom - AllSoftwareScrollViewportHeight;
SetAllSoftwareScrollY( nextScroll, smooth );
}
private void SetAllSoftwareScrollY( float scrollY, bool smooth = false )
{
_allSoftwareDesiredScrollY = scrollY.Clamp( 0f, AllSoftwareMaxScrollY );
_allSoftwareSmoothScrollActive = smooth;
_pendingAllSoftwareScrollApply = !smooth;
if ( AllSoftwareScroll == null )
return;
if ( smooth )
return;
AllSoftwareScroll.ScrollOffset = new Vector2( 0f, _allSoftwareDesiredScrollY * AllSoftwareScrollScale );
AllSoftwareScroll.ScrollVelocity = Vector2.Zero;
_lastAllSoftwareScrollY = _allSoftwareDesiredScrollY;
_pendingAllSoftwareScrollApply = false;
}
private void UpdateAllSoftwareScrollState()
{
if ( !_allSoftwareOpen || AllSoftwareScroll == null )
return;
UpdateAllSoftwareScrollbarDrag();
if ( _allSoftwareSmoothScrollActive )
{
float current = AllSoftwareScrollY;
float next = current.LerpTo( _allSoftwareDesiredScrollY, Time.Delta * AllSoftwareSmoothScrollRate );
if ( MathF.Abs( next - _allSoftwareDesiredScrollY ) < 0.5f )
{
next = _allSoftwareDesiredScrollY;
_allSoftwareSmoothScrollActive = false;
}
AllSoftwareScroll.ScrollOffset = new Vector2( 0f, next * AllSoftwareScrollScale );
AllSoftwareScroll.ScrollVelocity = Vector2.Zero;
_lastAllSoftwareScrollY = next;
QueueArtworkRefresh();
StateHasChanged();
return;
}
if ( _pendingAllSoftwareScrollApply )
{
SetAllSoftwareScrollY( _allSoftwareDesiredScrollY );
QueueArtworkRefresh();
StateHasChanged();
return;
}
float scrollY = AllSoftwareScrollY;
if ( MathF.Abs( scrollY - _lastAllSoftwareScrollY ) < 0.5f )
return;
_allSoftwareTitleDirection = Math.Sign( scrollY - _lastAllSoftwareScrollY );
_allSoftwareDesiredScrollY = scrollY;
_lastAllSoftwareScrollY = scrollY;
ClearAllSoftwareSelectionForMouseScroll();
QueueArtworkRefresh();
StateHasChanged();
}
private void OnAllSoftwareScrollbarMouseDown()
{
if ( !AllSoftwareShowsScrollbar || AllSoftwareScrollbar == null )
return;
UseMouseInAllSoftware();
_allSoftwareSmoothScrollActive = false;
_allSoftwareScrollbarDragging = true;
float thumbTop = AllSoftwareScrollbarThumb?.Box.Top ?? 0f;
float thumbBottom = AllSoftwareScrollbarThumb?.Box.Bottom ?? 0f;
float thumbHeight = MathF.Max( 1f, thumbBottom - thumbTop );
bool pressedThumb = Mouse.Position.y >= thumbTop && Mouse.Position.y <= thumbBottom;
_allSoftwareScrollbarDragOffsetY = pressedThumb ? Mouse.Position.y - thumbTop : thumbHeight * 0.5f;
UpdateAllSoftwareScrollbarDrag();
}
protected override void OnMouseMove( MousePanelEvent e )
{
base.OnMouseMove( e );
if ( !_allSoftwareScrollbarDragging )
return;
UpdateAllSoftwareScrollbarDrag();
e.StopPropagation();
}
protected override void OnMouseUp( MousePanelEvent e )
{
base.OnMouseUp( e );
if ( !_allSoftwareScrollbarDragging )
return;
_allSoftwareScrollbarDragging = false;
e.StopPropagation();
}
private void UpdateAllSoftwareScrollbarDrag()
{
if ( !_allSoftwareScrollbarDragging || AllSoftwareScrollbar == null || AllSoftwareScroll == null || !AllSoftwareShowsScrollbar )
return;
float trackHeight = MathF.Max( 1f, AllSoftwareScrollbar.Box.Rect.Height );
float thumbHeight = MathF.Max( 1f, AllSoftwareScrollbarThumbHeight * AllSoftwareScrollScale );
float travel = MathF.Max( 1f, trackHeight - thumbHeight );
float thumbTop = (Mouse.Position.y - AllSoftwareScrollbar.Box.Rect.Top - _allSoftwareScrollbarDragOffsetY).Clamp( 0f, travel );
float scrollY = (thumbTop / travel) * AllSoftwareMaxScrollY;
SetAllSoftwareScrollY( scrollY );
QueueArtworkRefresh();
StateHasChanged();
}
private bool IsAllSoftwareSelected( int index )
{
return ShowAllSoftwareSelection && _allSoftwareOpen && _allSoftwareSelection == index;
}
private bool ShouldShowAllSoftwareTitleOnTop( int index )
{
if ( index < 0 || index >= _allGames.Count || AllSoftwareGameRows <= 1 )
return false;
int row = index / AllSoftwareColumns;
if ( row == 0 )
return false;
if ( row == AllSoftwareGameRows - 1 )
return true;
return _allSoftwareTitleDirection > 0;
}
private string AllSoftwareAddButtonClass => "all-add-button" + (IsAllSoftwareAddSelected ? " selected" : "");
private void UseMouseInAllSoftware()
{
_input.ForceMouseMode();
_allSoftwareSelection = -1;
StateHasChanged();
}
private void ClearAllSoftwareSelectionForMouseScroll()
{
_input.ForceMouseMode();
_allSoftwareSelection = -1;
}
private void SelectFirstVisibleAllSoftwareItem( bool useGamepad )
{
if ( _allGames.Count == 0 )
{
SelectAllSoftwareItem( AllSoftwareAddIndex, useGamepad );
return;
}
int firstVisibleRow = Math.Max( 0, (int)MathF.Floor( (AllSoftwareScrollY - AllSoftwareGridVerticalPadding) / AllSoftwareRowStride ) );
int firstVisible = (firstVisibleRow * AllSoftwareColumns).Clamp( 0, _allGames.Count - 1 );
SelectAllSoftwareItem( firstVisible, useGamepad );
}
}