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

160 lines
3.8 KiB
C#

namespace Sandbox.UI;
internal class WorldInputInternal : PanelInput
{
internal static List<WeakReference<WorldInputInternal>> WorldInputs = new();
internal bool Enabled { get; set; } = true;
internal Ray Ray { get; set; }
internal bool MouseLeftPressed;
internal bool MouseRightPressed;
internal Vector2 MouseWheel;
internal bool UseMouseInput;
public WorldInputInternal()
{
WorldInputs.Add( new WeakReference<WorldInputInternal>( this, false ) );
}
internal static void Clear()
{
WorldInputs.Clear();
}
internal static void TickAll( IEnumerable<RootPanel> panels )
{
for ( int i = WorldInputs.Count - 1; i >= 0; i-- )
{
// Remove any dead references
if ( !WorldInputs[i].TryGetTarget( out var input ) )
{
WorldInputs.RemoveAt( i );
continue;
}
input.Tick( panels, true );
}
}
internal override void Tick( IEnumerable<RootPanel> panels, bool mouseIsActive )
{
if ( !Enabled )
{
SetHovered( null );
return;
}
bool hoveredAny = false;
var inputData = GetInputData();
List<RootPanel> worldPanels = new();
foreach ( var panel in panels.Where( p => p.ChildrenWantMouseInput ) )
{
if ( panel.RayToLocalPosition( Ray, out panel.WorldCursor, out panel.WorldDistance ) )
worldPanels.Add( panel );
}
// In order of distance, update our mouse on them
foreach ( var panel in worldPanels.OrderBy( x => x.WorldDistance ) )
{
inputData.MousePos = panel.WorldCursor;
if ( UpdateMouse( panel, inputData ) )
{
hoveredAny = true;
break;
}
}
if ( !hoveredAny )
{
SetHovered( null );
}
SimulateEvents();
}
internal override InputData GetInputData()
{
if ( UseMouseInput )
{
return base.GetInputData();
}
return new InputData
{
Mouse0 = MouseLeftPressed,
Mouse2 = MouseRightPressed,
MouseWheel = MouseWheel,
};
}
//
// Simulate some events, these are a bit different from how InputRouter
// handles it, so it's a bit shit that this is repeated but required for now.
//
//
// We only want to count as a double click if it was clicked within
// a small amount of pixels & on the same root panel.
// It's not a double click if you click, move the mouse, and then click again.
//
internal Panel LastClickRoot;
internal Vector2 LastClickPos;
internal RealTimeSince LastClickTimeSince;
// Bit more forgiving then normal panels ( shaky VR hands )
internal const float MaxAltClickDelta = 50.0f;
internal Queue<string> DoubleClicks = new();
internal Vector2 MouseMovement;
internal Vector2 LastMousePosition;
internal override bool UpdateMouse( RootPanel root, InputData data )
{
MouseMovement += LastMousePosition - data.MousePos;
LastMousePosition = data.MousePos;
var leftMousePressed = !MouseStates[0].Pressed && data.Mouse0;
if ( leftMousePressed )
{
// Are we a double clicker ( 250ms matches engine )
if ( LastClickTimeSince < 0.25f && LastClickRoot == root )
{
// let's be a lot more forgiving with the delta then on normal panels
// people can have shaky vr hands
var AltClickDelta = LastClickPos - data.MousePos;
if ( AltClickDelta.Length < MaxAltClickDelta / root.Scale )
{
DoubleClicks.Enqueue( "mouseleft" );
}
}
LastClickRoot = root;
LastClickPos = data.MousePos;
LastClickTimeSince = 0;
}
return base.UpdateMouse( root, data );
}
internal void SimulateEvents()
{
var listSize = DoubleClicks.Count;
for ( int i = 0; i < listSize; i++ )
if ( DoubleClicks.TryDequeue( out var e ) )
{
Hovered?.CreateEvent( new MousePanelEvent( "ondoubleclick", Hovered, e ) );
}
if ( MouseMovement != 0 )
{
// If we're pressing down on a panel we send all the mouse move events to that
var moveRecv = Hovered;
if ( Active != null ) moveRecv = Active;
moveRecv?.CreateEvent( new MousePanelEvent( "onmousemove", moveRecv, "none" ) );
MouseMovement = 0;
}
}
}