Files
sbox-public/engine/Sandbox.Engine/Systems/UI/Input/InputFocus.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

159 lines
3.3 KiB
C#

using Sandbox.Engine;
namespace Sandbox.UI;
/// <summary>
/// Handles input focus for <see cref="Panel"/>s.
/// </summary>
public class InputFocus
{
/// <summary>
/// The panel that currently has input focus.
/// </summary>
public static Panel Current
{
get => GlobalContext.Current.UISystem.CurrentFocus;
internal set => GlobalContext.Current.UISystem.CurrentFocus = value;
}
/// <summary>
/// The panel that will have the input focus next.
/// </summary>
public static Panel Next
{
get => GlobalContext.Current.UISystem.NextFocus;
internal set => GlobalContext.Current.UISystem.NextFocus = value;
}
static bool PendingChange
{
get => GlobalContext.Current.UISystem.FocusPendingChange;
set => GlobalContext.Current.UISystem.FocusPendingChange = value;
}
/// <summary>
/// Set the focus to this panel (or its nearest ancestor with AcceptsFocus).
/// Note that <see cref="Current"/> won't change until the next frame.
/// </summary>
public static bool Set( Panel panel )
{
return TrySetOrParent( panel );
}
/// <summary>
/// Clear focus away from this panel.
/// </summary>
public static bool Clear( Panel panel )
{
Next = null;
PendingChange = true;
Set( panel?.Parent );
return true;
}
/// <summary>
/// Clear keyboard focus
/// </summary>
public static bool Clear()
{
if ( Current == null )
return false;
Next = null;
PendingChange = true;
return true;
}
static bool TrySetOrParent( Panel panel )
{
if ( panel == null ) return false;
if ( Next == panel ) return true;
//
// Note that we're not judging eligibility based on styles here
// That happens in the Tick because those styles might not have been
// calculated yet.
//
if ( panel.AcceptsFocus )
{
Next = panel;
PendingChange = true;
return true;
}
return TrySetOrParent( panel.Parent );
}
internal static void Tick()
{
// TODO - maintain - make sure Current/Next can still be the focus
// move the focus up if they can't
// TODO - make sure current selected is a child of one of the root panels
// if the panel was removed then it's no good to us
//
// If our focus became uneligable then defocus
//
if ( Current != null )
{
if ( !IsPanelEligibleForFocus( Current ) )
{
if ( !PendingChange || Next == Current )
{
// TODO - should we shift focus to a parent
// or should that logic be in panels?
Next = null;
PendingChange = true;
}
}
}
//
// Don't swap to an uneligable panel
//
if ( PendingChange && Next != null )
{
if ( !Next.AcceptsFocus )
{
Next = null;
PendingChange = false;
}
}
if ( PendingChange )
{
PendingChange = false;
if ( Current != Next )
{
if ( Current != null )
{
Panel.Switch( PseudoClass.Focus, false, Current, Next );
Current.CreateEvent( new PanelEvent( "onblur", Current ) );
}
Current = Next;
Panel.Switch( PseudoClass.Focus, true, Current, null );
Current?.CreateEvent( new PanelEvent( "onfocus", Current ) );
}
}
//Log.Info( $"InputRouter.NeedsKeyboardInput = {InputRouter.NeedsKeyboardInput} ({Current})" );
Next = null;
}
static bool IsPanelEligibleForFocus( Panel panel )
{
if ( !panel.IsVisible ) return false;
if ( !panel.AcceptsFocus ) return false;
return true;
}
}