UI/Inventory/InventorySlot.razor
@using Sandbox;
@using Sandbox.UI;
@namespace Sandbox
@inherits Panel
<root>
@if ( !Input.UsingController )
{
<label class="index">@(Index + 1)</label>
}
@{
var weapon = GetWeapon();
}
@if (weapon.IsValid())
{
<div class="icon" @ref="IconPanel"></div>
<div class="name">@weapon.DisplayName</div>
}
</root>
@code
{
public PlayerInventory Inventory { get; set; }
public bool Active { get; set; }
public bool Hovered { get; set; }
public int Index { get; set; }
Panel IconPanel { get; set; }
static InventorySlot _hoveredDragSlot;
public override bool WantsDrag => true;
protected override void OnDragStart( DragEvent e )
{
var weapon = GetWeapon();
if ( !weapon.IsValid() ) return;
var icon = weapon.InventoryIconOverride ?? weapon.DisplayIcon?.ResourcePath;
DragHandler.StartDragging( new DragData
{
Source = this,
Icon = icon,
Title = weapon.DisplayName
} );
}
protected override void OnDragEnd( DragEvent e )
{
var data = DragHandler.Current;
DragHandler.StopDragging();
base.OnDragEnd( e );
// OnDrop on the target already handled the swap.
if ( data?.Handled == true )
{
if ( data.Source is not null ) data.Source.UserData = null;
return;
}
// Mouse is hovering over a different slot — OnDrop will fire and handle the swap.
// Don't clear UserData yet so OnDrop can still retrieve it via e.Target.UserData.
if ( _hoveredDragSlot is not null && _hoveredDragSlot != this )
return;
// No slot handled it — clean up.
if ( data?.Source is not null ) data.Source.UserData = null;
// If the mouse is still over this slot the user dragged back to the source — no-op.
var mousePos = Mouse.Position * ScaleFromScreen;
if ( Box.RectOuter.IsInside( mousePos ) ) return;
var weapon = GetWeapon();
if ( !Inventory.Drop( weapon ) && weapon.IsValid() )
Inventory.Remove( weapon );
}
BaseCarryable GetWeapon()
{
if (!Inventory.IsValid()) return null;
return Inventory.GetSlot(Index);
}
bool IsSpawnMenuOpen()
{
var host = Game.ActiveScene.Get<SpawnMenuHost>();
return host?.Panel?.HasClass("open") ?? false;
}
public override void Tick()
{
base.Tick();
var weapon = GetWeapon();
var hasWeapon = weapon.IsValid();
var spawnMenuOpen = IsSpawnMenuOpen();
SetClass("droppable", spawnMenuOpen);
SetClass("active", Hovered || Active);
SetClass("empty", !hasWeapon);
SetClass("selected", Active && hasWeapon);
SetClass("hovered", Hovered && hasWeapon);
SetClass("switchable", hasWeapon && weapon.CanSwitch());
SetClass("no-tint", hasWeapon && weapon is SpawnerWeapon);
if (hasWeapon && IconPanel is not null)
{
var iconOverride = weapon.InventoryIconOverride;
if (!string.IsNullOrEmpty(iconOverride))
{
IconPanel.Style.SetBackgroundImage(iconOverride);
}
else
{
IconPanel.Style.SetBackgroundImage(weapon.DisplayIcon);
}
}
}
protected override void OnDragEnter(PanelEvent e)
{
AddClass("drag-hover");
_hoveredDragSlot = this;
}
protected override void OnDragLeave(PanelEvent e)
{
RemoveClass("drag-hover");
if ( _hoveredDragSlot == this ) _hoveredDragSlot = null;
}
protected override void OnDrop( PanelEvent e )
{
RemoveClass( "drag-hover" );
_hoveredDragSlot = null;
// Use DragHandler.Current first; fall back to UserData for the case where
// OnDragEnd fired first and cleared Current (but preserved UserData).
var data = DragHandler.Current ?? e.Target.UserData as DragData;
// Clean up UserData now that the drop is being processed.
if ( data?.Source is not null ) data.Source.UserData = null;
if ( data is null ) return;
// Hotbar-to-hotbar: move or swap the dragged slot with this slot.
if ( data.Source is InventorySlot sourceSlot && sourceSlot.Inventory == Inventory )
{
data.Handled = true;
Inventory.MoveSlot( sourceSlot.Index, Index );
return;
}
GameManager.GiveSpawnerWeaponAt( data.Type, data.Path, Index, data.Data as string, data.Icon, data.Title );
}
protected override int BuildHash() => HashCode.Combine(GetWeapon(), Hovered, Active, Input.UsingController);
}