using Sandbox.Engine; namespace Sandbox.UI; /// /// Handles input focus for s. /// public class InputFocus { /// /// The panel that currently has input focus. /// public static Panel Current { get => GlobalContext.Current.UISystem.CurrentFocus; internal set => GlobalContext.Current.UISystem.CurrentFocus = value; } /// /// The panel that will have the input focus next. /// 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; } /// /// Set the focus to this panel (or its nearest ancestor with AcceptsFocus). /// Note that won't change until the next frame. /// public static bool Set( Panel panel ) { return TrySetOrParent( panel ); } /// /// Clear focus away from this panel. /// public static bool Clear( Panel panel ) { Next = null; PendingChange = true; Set( panel?.Parent ); return true; } /// /// Clear keyboard focus /// 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; } }