Files
sbox-public/engine/Sandbox.Engine/Systems/UI/Panel/Panel.Input.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

298 lines
9.2 KiB
C#

namespace Sandbox.UI;
public partial class Panel
{
/// <summary>
/// Current mouse position local to this panels top left corner.
/// </summary>
[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;
}
}
/// <summary>
/// Called by <see cref="PanelInput.CheckHover(Panel, Vector2, ref Panel)" /> to transform
/// the current mouse position using the panel's LocalMatrix (by default). This can be overriden for special cases.
/// </summary>
/// <param name="pos"></param>
/// <returns></returns>
public virtual Vector2 GetTransformPosition( Vector2 pos )
{
return LocalMatrix?.Transform( pos ) ?? pos;
}
/// <summary>
/// Whether given screen position is within this panel. This will accurately handle border radius as well.
/// </summary>
/// <param name="pos">The position to test, in screen coordinates.</param>
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;
}
/// <summary>
/// Whether the given rect is inside this panels bounds. (<see cref="Box.Rect"/>)
/// </summary>
/// <param name="rect">The rect to test, which should have screen-space coordinates.</param>
/// <param name="fullyInside"><see langword="true"/> to test if the given rect is completely inside the panel. <see langword="false"/> to test for an intersection.</param>
public bool IsInside( Rect rect, bool fullyInside )
{
return rect.IsInside( Box.Rect, fullyInside );
}
/// <summary>
/// False by default, can this element accept keyboard focus. If an element accepts
/// focus it'll be able to receive keyboard input.
/// </summary>
[Property]
public bool AcceptsFocus { get; set; }
/// <summary>
/// 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.
/// </summary>
[Property]
public PanelInputType ButtonInput { get; set; }
/// <summary>
/// False by default. Anything that is capable of accepting IME input should return true. Which is probably just a TextEntry.
/// </summary>
[Hide]
public virtual bool AcceptsImeInput => false;
/// <summary>
/// Give input focus to this panel.
/// </summary>
public bool Focus()
{
return InputFocus.Set( this );
}
/// <summary>
/// Remove input focus from this panel.
/// </summary>
public bool Blur()
{
return InputFocus.Clear( this );
}
/// <summary>
/// Called when any button, mouse (except for mouse4/5) and keyboard, are pressed or depressed while hovering this panel.
/// </summary>
public virtual void OnButtonEvent( ButtonEvent e )
{
Parent?.OnButtonEvent( e );
}
/// <summary>
/// Called when a printable character has been typed (pressed) while this panel has input focus. (<see cref="Focus"/>)
/// </summary>
public virtual void OnKeyTyped( char k )
{
Parent?.OnKeyTyped( k );
}
/// <summary>
/// Called when any keyboard button has been typed (pressed) while this panel has input focus. (<see cref="Focus"/>)
/// </summary>
public virtual void OnButtonTyped( ButtonEvent e )
{
Parent?.OnButtonTyped( e );
}
/// <summary>
/// Called when the user presses CTRL+V while this panel has input focus.
/// </summary>
/// <param name="text"></param>
public virtual void OnPaste( string text )
{
Parent?.OnPaste( text );
}
/// <summary>
/// If we have a value that can be copied to the clipboard, return it here.
/// </summary>
public virtual string GetClipboardValue( bool cut )
{
if ( AllowChildSelection )
return CollectSelectedChildrenText( this );
if ( Parent != null )
return Parent.GetClipboardValue( cut );
return null;
}
/// <summary>
/// Called when the player scrolls their mouse wheel while hovering this panel.
/// </summary>
/// <param name="value">The scroll wheel delta. Positive values are scrolling down, negative - up.</param>
public virtual void OnMouseWheel( Vector2 value )
{
if ( TryScroll( value ) )
return;
Parent?.OnMouseWheel( value );
}
/// <summary>
/// Called from <see cref="OnMouseWheel"/> to try to scroll.
/// </summary>
/// <param name="value">The scroll wheel delta. Positive values are scrolling down, negative - up.</param>
/// <returns>Return true to NOT propagate the event to the <see cref="Parent"/>.</returns>
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;
}
/// <summary>
/// Scroll to the bottom, if the panel has scrolling enabled.
/// </summary>
/// <returns>Whether we scrolled to the bottom or not.</returns>
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; }
/// <summary>
/// Captures the mouse cursor while active. The cursor will be hidden and will be stuck in place.
/// <para>You will want to use <see cref="Mouse.Delta"/> in
/// <see cref="Panel.Tick"/> while <see cref="HasMouseCapture"/> to read mouse movements.</para>
/// <para>You can call this from <see cref="OnButtonEvent"/> for mouse clicks.</para>
/// </summary>
/// <param name="b">Whether to enable or disable the capture.</param>
public void SetMouseCapture( bool b )
{
if ( b )
{
MouseCapture = this;
return;
}
if ( MouseCapture == this )
{
MouseCapture = null;
return;
}
}
/// <summary>
/// Whether this panel is capturing the mouse cursor. See <see cref="SetMouseCapture"/>.
/// </summary>
[Hide]
public bool HasMouseCapture => MouseCapture == this;
//
// These are used by the input system as an optimization
//
internal Vector2 WorldCursor;
internal float WorldDistance = float.MaxValue;
/// <summary>
/// Transform a ray in 3D space to a position on the panel. This is used for world panel input.
/// </summary>
/// <param name="ray">The ray in 3D world space to test against this panel.</param>
/// <param name="position">Position on the panel where the intersection happened, local to the panel's top left corner.</param>
/// <param name="distance">Distance from the ray's origin to the intersection in 3D space.</param>
/// <returns>Return true if a hit/intersection was detected.</returns>
public virtual bool RayToLocalPosition( Ray ray, out Vector2 position, out float distance )
{
position = default;
distance = default;
return false;
}
}