using System.Reflection; namespace Sandbox.UI; public partial class Panel { internal struct EventCallback { public string EventName; public Action BaseAction; public Action Action; public bool Automatic; public Action Event; public Panel Panel; public Panel Context; } internal List EventListeners { get; set; } /// /// Called on creation and hotload to delete and re-initialize event listeners. /// protected virtual void InitializeEvents() { EventListeners?.RemoveAll( x => x.Automatic ); if ( Game.TypeLibrary == null || !Game.TypeLibrary.TryGetType( GetType(), out var typeDescription ) ) return; foreach ( var methodDesc in typeDescription.Members.OfType() ) { if ( methodDesc.IsStatic ) continue; var pea = methodDesc.Attributes.OfType().FirstOrDefault(); if ( pea == null ) continue; var method = methodDesc.MemberInfo as MethodInfo; var event_name = methodDesc.Name.ToLower(); var args = method.GetParameters(); // todo cache var ret = method.ReturnParameter; if ( event_name.EndsWith( "event" ) ) event_name = event_name[..^5]; if ( pea != null && pea.Name != null ) event_name = pea.Name.ToLower(); if ( args.Length == 1 ) { var argType = args[0].ParameterType; var argcache = new object[1]; if ( argType == typeof( PanelEvent ) ) { AddAutomaticEventListener( event_name, ( x ) => { argcache[0] = x; method.Invoke( this, argcache ); } ); } else { AddAutomaticEventListener( event_name, ( x ) => { argcache[0] = Convert.ChangeType( x.Value, argType ); var response = method.Invoke( this, argcache ); // If the response is a bool, and false, stop propogation if ( response != null && response is bool bReturnedValue ) { x.Propagate = x.Propagate && bReturnedValue; } } ); } } else if ( args.Length == 0 ) { AddAutomaticEventListener( event_name, ( x ) => { var response = method.Invoke( this, null ); // If the response is a bool, and false, stop propogation if ( response != null && response is bool bReturnedValue ) { x.Propagate = x.Propagate && bReturnedValue; } } ); } else { Log.Warning( $"PanelEvent {method} - couldn't set up (too many arguments)" ); } } } internal void AddAutomaticEventListener( string name, Action e ) { EventListeners ??= new List(); var ev = new EventCallback { EventName = name, Action = e, Automatic = true }; EventListeners.Add( ev ); } internal void RemoveEventListener( string name ) { EventListeners?.RemoveAll( x => x.EventName == name ); } /// /// Runs given callback when the given event is triggered. /// public void AddEventListener( string eventName, Action e ) { AddEventListener( new EventCallback { EventName = eventName, Action = e } ); } /// /// Runs given callback when the given event is triggered, without access to the . /// public void AddEventListener( string eventName, Action action ) { AddEventListener( new EventCallback { EventName = eventName, BaseAction = action } ); } internal void AddEventListener( EventCallback eventCallback ) { EventListeners ??= new List(); EventListeners.Add( eventCallback ); } List PendingEvents; internal void RunPendingEvents() { if ( PendingEvents is null || PendingEvents.Count == 0 ) return; for ( int i = 0; i < PendingEvents.Count; i++ ) { var e = PendingEvents[i]; if ( e.Time > TimeNow ) continue; PendingEvents.RemoveAt( i ); i--; try { OnEvent( e ); } catch ( System.Exception ex ) { Log.Error( ex, $"\"{ex.Message}\" when running panel event \"{e.Name}\" from \"{e.Target}\"" ); } } } /// /// Create a new event and pass it to the panels event queue. /// /// Event name. /// Event value. /// Time, in seconds, to wait before firing the event.
/// All subsequent calls to with the same event /// name will update the original event instead of creating a new event, until it finally triggers. public virtual void CreateEvent( string name, object value = null, float? debounce = null ) { var e = PendingEvents?.FirstOrDefault( x => x.Name == name ); if ( e == null ) { e = new PanelEvent( name, this ); CreateEvent( e ); } e.Value = value; if ( debounce.HasValue ) { e.Time = TimeNow + debounce.Value; } } /// /// Pass given event to the event queue. /// public virtual void CreateEvent( PanelEvent evnt ) { PendingEvents ??= new List(); PendingEvents.Add( evnt ); } /// /// Called when various s happen. Handles event listeners and many standard events by default. /// protected virtual void OnEvent( PanelEvent e ) { e.This = this; if ( e is CopyEvent || e is CutEvent ) { var text = GetClipboardValue( e is CutEvent ); if ( text != null ) { NativeEngine.EngineGlobal.Plat_SetClipboardText( text ); } } if ( e is PasteEvent paste ) { OnPaste( paste.ClipboardValue ); } if ( e is MousePanelEvent mpe ) { if ( e.Is( "onclick" ) ) OnClick( mpe ); if ( e.Is( "onmiddleclick" ) ) OnMiddleClick( mpe ); if ( e.Is( "onrightclick" ) ) OnRightClick( mpe ); if ( e.Is( "onmousedown" ) ) OnMouseDown( mpe ); if ( e.Is( "onmouseup" ) ) OnMouseUp( mpe ); if ( e.Is( "ondoubleclick" ) ) OnDoubleClick( mpe ); if ( e.Is( "onmousemove" ) ) OnMouseMove( mpe ); if ( e.Is( "onmouseover" ) ) OnMouseOver( mpe ); if ( e.Is( "onmouseout" ) ) OnMouseOut( mpe ); if ( !e.Is( "onmousemove" ) ) { razorTreeDirty = true; } } if ( e is DragEvent de ) { InternalDragEvent( de ); } if ( e.Is( "onfocus" ) ) OnFocus( e ); if ( e.Is( "onblur" ) ) OnBlur( e ); if ( e.Is( "onback" ) ) OnBack( e ); if ( e.Is( "onforward" ) ) OnForward( e ); if ( e.Is( "onescape" ) ) OnEscape( e ); if ( e is SelectionEvent se ) { if ( e.Is( "ondragselect" ) ) OnDragSelect( se ); } if ( !e.Propagate ) return; if ( EventListeners != null ) { foreach ( var listener in EventListeners ) { if ( !e.Is( listener.EventName ) ) continue; listener.Event?.Invoke( listener, e ); listener.Action?.Invoke( e ); listener.BaseAction?.Invoke(); } } if ( !e.Propagate ) return; Parent?.OnEvent( e ); } /// /// Called when the player releases their left mouse button (Mouse 1) while hovering this panel. /// protected virtual void OnClick( MousePanelEvent e ) { } /// /// Called when the player releases their middle mouse button (Mouse 3) while hovering this panel. /// protected virtual void OnMiddleClick( MousePanelEvent e ) { } /// /// Called when the player releases their right mouse button (Mouse 2) while hovering this panel. /// protected virtual void OnRightClick( MousePanelEvent e ) { } /// /// Called when the player presses down the left or right mouse buttons while hovering this panel. /// protected virtual void OnMouseDown( MousePanelEvent e ) { } /// /// Called when the player releases left or right mouse button. /// protected virtual void OnMouseUp( MousePanelEvent e ) { } /// /// Called when the player double clicks the panel with the left mouse button. /// protected virtual void OnDoubleClick( MousePanelEvent e ) { } /// /// Called when the cursor moves while hovering this panel. /// protected virtual void OnMouseMove( MousePanelEvent e ) { } /// /// Called when the cursor enters this panel. /// protected virtual void OnMouseOver( MousePanelEvent e ) { } /// /// Called when the cursor leaves this panel. /// protected virtual void OnMouseOut( MousePanelEvent e ) { } /// /// Called when the player presses the "Back" button while hovering this panel, which is typically "mouse 5", aka one of the mouse buttons on its side. /// protected virtual void OnBack( PanelEvent e ) { } /// /// Called when the player presses the "Forward" button while hovering this panel, which is typically "mouse 4", aka one of the mouse buttons on its side. /// protected virtual void OnForward( PanelEvent e ) { } /// /// Called when the escape key is pressed /// protected virtual void OnEscape( PanelEvent e ) { if ( HasFocus ) Blur(); } /// /// Called when this panel receives input focus. /// protected virtual void OnFocus( PanelEvent e ) { } /// /// Called when this panel loses input focus. /// protected virtual void OnBlur( PanelEvent e ) { } }