namespace Sandbox.UI; public partial class Panel { /// /// Current mouse position local to this panels top left corner. /// [Hide] public Vector2 MousePosition { get { if ( FindRootPanel() is not RootPanel root ) return default; var mp = root.MousePos; if ( GlobalMatrix.HasValue ) { mp = GlobalMatrix.Value.Transform( mp ); } return mp - Box.Rect.Position; } } /// /// Called by to transform /// the current mouse position using the panel's LocalMatrix (by default). This can be overriden for special cases. /// /// /// public virtual Vector2 GetTransformPosition( Vector2 pos ) { return LocalMatrix?.Transform( pos ) ?? pos; } /// /// Whether given screen position is within this panel. This will accurately handle border radius as well. /// /// The position to test, in screen coordinates. public bool IsInside( Vector2 pos ) { var rect = Box.Rect; if ( pos.x < rect.Left || pos.x > rect.Right ) return false; if ( pos.y < rect.Top || pos.y > rect.Bottom ) return false; var s = ComputedStyle; if ( s == null ) return false; pos.x -= rect.Left; pos.y -= rect.Top; if ( s.BorderTopLeftRadius.HasValue && s.BorderTopLeftRadius.Value.Unit > 0 ) { var r = s.BorderTopLeftRadius.Value.GetPixels( (rect.Width + rect.Height) * 0.5f ); r = MathF.Min( MathF.Min( r, rect.Width / 2.0f ), rect.Height / 2.0f ); var c = new Vector2( r, r ); if ( pos.x < c.x && pos.y < c.y && Vector2.Distance( pos, c ) > r ) return false; } if ( s.BorderTopRightRadius.HasValue && s.BorderTopRightRadius.Value.Unit > 0 ) { var r = s.BorderTopRightRadius.Value.GetPixels( (rect.Width + rect.Height) * 0.5f ); r = MathF.Min( MathF.Min( r, rect.Width / 2.0f ), rect.Height / 2.0f ); var c = new Vector2( rect.Width - r, r ); if ( pos.x > c.x && pos.y < c.y && Vector2.Distance( pos, c ) > r ) return false; } if ( s.BorderBottomRightRadius.HasValue && s.BorderBottomRightRadius.Value.Unit > 0 ) { var r = s.BorderBottomRightRadius.Value.GetPixels( (rect.Width + rect.Height) * 0.5f ); r = MathF.Min( MathF.Min( r, rect.Width / 2.0f ), rect.Height / 2.0f ); var c = new Vector2( rect.Width - r, rect.Height - r ); if ( pos.x > c.x && pos.y > c.y && Vector2.Distance( pos, c ) > r ) return false; } if ( s.BorderBottomLeftRadius.HasValue && s.BorderBottomLeftRadius.Value.Unit > 0 ) { var r = s.BorderBottomLeftRadius.Value.GetPixels( (rect.Width + rect.Height) * 0.5f ); r = MathF.Min( MathF.Min( r, rect.Width / 2.0f ), rect.Height / 2.0f ); var c = new Vector2( r, rect.Height - r ); if ( pos.x < c.x && pos.y > c.y && Vector2.Distance( pos, c ) > r ) return false; } return true; } /// /// Whether the given rect is inside this panels bounds. () /// /// The rect to test, which should have screen-space coordinates. /// to test if the given rect is completely inside the panel. to test for an intersection. public bool IsInside( Rect rect, bool fullyInside ) { return rect.IsInside( Box.Rect, fullyInside ); } /// /// False by default, can this element accept keyboard focus. If an element accepts /// focus it'll be able to receive keyboard input. /// [Property] public bool AcceptsFocus { get; set; } /// /// Describe what to do with keyboard input. The default is InputMode.UI which means that when /// focused, this panel will receive Keys Typed and Button Events. /// If you set this to InputMode.Game, this panel will redirect its inputs to the game, which means /// for example that if you're focused on this panel and press space, it'll send the jump button to the game. /// [Property] public PanelInputType ButtonInput { get; set; } /// /// False by default. Anything that is capable of accepting IME input should return true. Which is probably just a TextEntry. /// [Hide] public virtual bool AcceptsImeInput => false; /// /// Give input focus to this panel. /// public bool Focus() { return InputFocus.Set( this ); } /// /// Remove input focus from this panel. /// public bool Blur() { return InputFocus.Clear( this ); } /// /// Called when any button, mouse (except for mouse4/5) and keyboard, are pressed or depressed while hovering this panel. /// public virtual void OnButtonEvent( ButtonEvent e ) { Parent?.OnButtonEvent( e ); } /// /// Called when a printable character has been typed (pressed) while this panel has input focus. () /// public virtual void OnKeyTyped( char k ) { Parent?.OnKeyTyped( k ); } /// /// Called when any keyboard button has been typed (pressed) while this panel has input focus. () /// public virtual void OnButtonTyped( ButtonEvent e ) { Parent?.OnButtonTyped( e ); } /// /// Called when the user presses CTRL+V while this panel has input focus. /// /// public virtual void OnPaste( string text ) { Parent?.OnPaste( text ); } /// /// If we have a value that can be copied to the clipboard, return it here. /// public virtual string GetClipboardValue( bool cut ) { if ( AllowChildSelection ) return CollectSelectedChildrenText( this ); if ( Parent != null ) return Parent.GetClipboardValue( cut ); return null; } /// /// Called when the player scrolls their mouse wheel while hovering this panel. /// /// The scroll wheel delta. Positive values are scrolling down, negative - up. public virtual void OnMouseWheel( Vector2 value ) { if ( TryScroll( value ) ) return; Parent?.OnMouseWheel( value ); } /// /// Called from to try to scroll. /// /// The scroll wheel delta. Positive values are scrolling down, negative - up. /// Return true to NOT propagate the event to the . public bool TryScroll( Vector2 value ) { if ( ComputedStyle == null ) return false; if ( !HasScrollY && !HasScrollX ) return false; // If we're not scrolling in the same direction that this panel overflows in, ignore if ( ComputedStyle.OverflowX != OverflowMode.Scroll && value.x != 0 ) return false; if ( ComputedStyle.OverflowY != OverflowMode.Scroll && value.y != 0 ) return false; var velocityAdd = Vector2.Zero; if ( ComputedStyle.OverflowX == OverflowMode.Scroll && HasScrollX ) velocityAdd += new Vector2( value.x * -20, 0 ); if ( ComputedStyle.OverflowY == OverflowMode.Scroll && HasScrollY ) velocityAdd += new Vector2( 0, value.y * 20 ); velocityAdd *= (1 + ScrollVelocity.Length / 100.0f); ScrollVelocity += velocityAdd; if ( velocityAdd.Length.AlmostEqual( 0 ) ) return false; return true; } /// /// Scroll to the bottom, if the panel has scrolling enabled. /// /// Whether we scrolled to the bottom or not. public bool TryScrollToBottom() { if ( ComputedStyle == null ) return false; if ( !HasScrollY ) return false; ScrollOffset = new Vector2( ScrollOffset.x, ScrollSize.y ); IsScrollAtBottom = true; ScrollVelocity = new Vector2( 0, 0 ); return true; } internal static Panel MouseCapture { get; private set; } /// /// Captures the mouse cursor while active. The cursor will be hidden and will be stuck in place. /// You will want to use in /// while to read mouse movements. /// You can call this from for mouse clicks. /// /// Whether to enable or disable the capture. public void SetMouseCapture( bool b ) { if ( b ) { MouseCapture = this; return; } if ( MouseCapture == this ) { MouseCapture = null; return; } } /// /// Whether this panel is capturing the mouse cursor. See . /// [Hide] public bool HasMouseCapture => MouseCapture == this; // // These are used by the input system as an optimization // internal Vector2 WorldCursor; internal float WorldDistance = float.MaxValue; /// /// Transform a ray in 3D space to a position on the panel. This is used for world panel input. /// /// The ray in 3D world space to test against this panel. /// Position on the panel where the intersection happened, local to the panel's top left corner. /// Distance from the ray's origin to the intersection in 3D space. /// Return true if a hit/intersection was detected. public virtual bool RayToLocalPosition( Ray ray, out Vector2 position, out float distance ) { position = default; distance = default; return false; } }