Files
Unreal_EmberWild/Source/Survival_projet/Private/InventorySlotWidget.cpp
T
Mathew 41d6e6a7f3 (Feat) Add Storage Chests + Item Details Panel
Coffres : AStorageContainer, un UInventoryComponent configure en conteneur,
affiche comme panneau de WBP_InventoryScreen plutot que dans un ecran a lui.
Deplacement unifie par UInventoryComponent::TransferSlot / TransferAllTo.

Panneau de detail : UItemDetailsWidget, pose DANS WBP_Inventory a droite de la
grille. Icone, nom, separation, description, barre d'usure. Il recoit un couple
(inventaire, index) et s'abonne a OnInventoryChanged, donc une charge consommee
sous le curseur se voit.

Une case ne connait plus sa grille : elle diffuse OnHoverChanged. La barre
rapide s'y abonne aussi et relaie par le PlayerController, seul a posseder a la
fois le HUD et l'ecran. Le panneau efface la case qu'on QUITTE et non lui-meme,
sinon passer de la barre a la grille viderait ce qui vient d'etre affiche.

L'ecran allume et eteint le panneau (SetItemDetailsEnabled) : WBP_Inventory
etant instancie deux fois en mode coffre, une case a cocher par instance serait
un bug qui attend qu'on oublie de la decocher.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 22:51:24 +02:00

246 lines
8.0 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#include "InventorySlotWidget.h"
#include "Components/Image.h"
#include "Components/ProgressBar.h"
#include "Components/TextBlock.h"
#include "Components/Widget.h"
#include "InventoryDragDropOperation.h"
#include "ItemDataAsset.h"
void UInventorySlotWidget::NativeConstruct()
{
Super::NativeConstruct();
// Force en C++ plutot que de dependre d'un reglage du Blueprint : une case
// non testable a la souris ne recevrait jamais NativeOnMouseButtonDown, et
// le glisser-deposer serait mysterieusement inerte.
// NativeConstruct s'execute APRES ConfigureAsDragVisual() pour un fantome
// (le widget n'est construit qu'une fois ajoute a la couche de drag),
// d'ou le test : sans lui, on remettrait le fantome en Visible et il
// intercepterait le depot destine a la case du dessous.
SetVisibility(bIsDragVisual ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Visible);
}
void UInventorySlotWidget::SetSelected(bool bSelected)
{
if (SelectionBorder)
{
SelectionBorder->SetVisibility(bSelected ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Collapsed);
}
}
void UInventorySlotWidget::ConfigureAsDragVisual()
{
bIsDragVisual = true;
// Le cadre de selection appartient a la barre, pas a l'objet.
if (SelectionBorder)
{
SelectionBorder->SetVisibility(ESlateVisibility::Collapsed);
}
// Le fond de case appartient a la grille, pas a l'objet. Le trainer sous
// le curseur donne l'impression de deplacer la case elle-meme.
if (SlotBackground)
{
SlotBackground->SetVisibility(ESlateVisibility::Collapsed);
}
SetVisibility(ESlateVisibility::HitTestInvisible);
}
void UInventorySlotWidget::InitSlot(UInventoryComponent* InInventory, int32 InSlotIndex)
{
OwningInventory = InInventory;
SlotIndex = InSlotIndex;
}
void UInventorySlotWidget::SetQuickTransferTarget(UInventoryComponent* InTarget)
{
QuickTransferTarget = InTarget;
}
void UInventorySlotWidget::NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
Super::NativeOnMouseEnter(InGeometry, InMouseEvent);
OnHoverChanged.Broadcast(SlotIndex, true);
}
void UInventorySlotWidget::NativeOnMouseLeave(const FPointerEvent& InMouseEvent)
{
Super::NativeOnMouseLeave(InMouseEvent);
OnHoverChanged.Broadcast(SlotIndex, false);
}
void UInventorySlotWidget::SetSlot(const FInventorySlot& InSlot)
{
CachedSlot = InSlot;
// Filet de securite : toute mise a jour de contenu remet la case en pleine
// opacite, meme si un glisser s'est termine par un chemin imprevu.
if (!bIsDragVisual)
{
SetRenderOpacity(1.f);
}
const bool bEmpty = InSlot.IsEmpty();
if (ItemIcon)
{
// Hidden et non Collapsed : on veut que la case garde sa taille dans
// la grille meme vide, sinon la mise en page sauterait a chaque
// ramassage.
if (bEmpty || !InSlot.Item->Icon)
{
ItemIcon->SetVisibility(ESlateVisibility::Hidden);
}
else
{
ItemIcon->SetBrushFromTexture(InSlot.Item->Icon, /*bMatchSize=*/false);
ItemIcon->SetVisibility(ESlateVisibility::HitTestInvisible);
}
}
if (QuantityText)
{
// On n'affiche pas "x1" : ca alourdit la grille pour rien.
if (bEmpty || InSlot.Quantity <= 1)
{
QuantityText->SetVisibility(ESlateVisibility::Hidden);
}
else
{
QuantityText->SetText(FText::AsNumber(InSlot.Quantity));
QuantityText->SetVisibility(ESlateVisibility::HitTestInvisible);
}
}
if (UsesBar)
{
// Collapsed et non Hidden : une barre d'usure sur un objet qui n'en a
// pas ne doit meme pas reserver sa place au bas de la case.
const int32 MaxUses = bEmpty ? 1 : FMath::Max(1, InSlot.Item->MaxUses);
if (MaxUses <= 1)
{
UsesBar->SetVisibility(ESlateVisibility::Collapsed);
}
else
{
UsesBar->SetPercent(FMath::Clamp(static_cast<float>(InSlot.RemainingUses) / static_cast<float>(MaxUses), 0.f, 1.f));
UsesBar->SetVisibility(ESlateVisibility::HitTestInvisible);
}
}
}
FReply UInventorySlotWidget::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
// Clic droit : transfert rapide vers l'inventaire d'en face, quand il y en
// a un. Traite avant le glisser pour que le bouton droit ne demarre jamais
// de DetectDrag -- ce qui laisserait une operation orpheline en cours.
if (!CachedSlot.IsEmpty() && InMouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
{
if (QuickTransferTarget.IsValid() && OwningInventory.IsValid() && SlotIndex != INDEX_NONE)
{
UInventoryComponent::QuickTransferSlot(OwningInventory.Get(), SlotIndex, QuickTransferTarget.Get());
return FReply::Handled();
}
}
if (CachedSlot.IsEmpty() || !InMouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
{
return Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);
}
// On ne demarre pas le glisser tout de suite : DetectDrag attend que la
// souris bouge reellement. Sans ca, un simple clic serait interprete comme
// un debut de glisser et on ne pourrait jamais faire autre chose.
return FReply::Handled().DetectDrag(TakeWidget(), EKeys::LeftMouseButton);
}
void UInventorySlotWidget::NativeOnDragDetected(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent, UDragDropOperation*& OutOperation)
{
Super::NativeOnDragDetected(InGeometry, InMouseEvent, OutOperation);
if (CachedSlot.IsEmpty() || !OwningInventory.IsValid() || SlotIndex == INDEX_NONE)
{
return;
}
// Modificateurs au moment de la saisie : Maj pour la moitie, Ctrl pour un
// seul. Conventions etablies du genre, le joueur les connait deja.
int32 DraggedQuantity = CachedSlot.Quantity;
if (InMouseEvent.IsControlDown())
{
DraggedQuantity = 1;
}
else if (InMouseEvent.IsShiftDown())
{
// Moitie arrondie au superieur : sur une pile de 7 on emporte 4.
// C'est la convention de Minecraft et consorts.
DraggedQuantity = (CachedSlot.Quantity + 1) / 2;
}
UInventoryDragDropOperation* Operation = NewObject<UInventoryDragDropOperation>();
Operation->SourceSlotIndex = SlotIndex;
Operation->Quantity = DraggedQuantity;
Operation->SourceInventory = OwningInventory;
Operation->Pivot = EDragPivot::CenterCenter;
// Le fantome sous le curseur est une copie de cette meme case : aucun
// widget supplementaire a maintenir, et l'apparence reste coherente.
// Il affiche la quantite EMPORTEE, pas la pile complete.
if (UInventorySlotWidget* Visual = CreateWidget<UInventorySlotWidget>(GetOwningPlayer(), GetClass()))
{
FInventorySlot VisualSlot = CachedSlot;
VisualSlot.Quantity = DraggedQuantity;
Visual->SetSlot(VisualSlot);
Visual->ConfigureAsDragVisual();
Visual->SetRenderOpacity(DragVisualOpacity);
Operation->DefaultDragVisual = Visual;
}
// On n'estompe la case source que si la pile part en entier. Lors d'une
// division une partie reste sur place, et la faire palir serait mentir
// sur ce qu'elle contient encore.
if (DraggedQuantity >= CachedSlot.Quantity)
{
SetRenderOpacity(DraggedSlotOpacity);
}
// Les deux issues possibles remettent l'opacite : depot reussi, ou
// abandon hors de la grille.
Operation->OnDrop.AddDynamic(this, &UInventorySlotWidget::HandleDragFinished);
Operation->OnDragCancelled.AddDynamic(this, &UInventorySlotWidget::HandleDragFinished);
OutOperation = Operation;
}
void UInventorySlotWidget::HandleDragFinished(UDragDropOperation* Operation)
{
SetRenderOpacity(1.f);
}
bool UInventorySlotWidget::NativeOnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation)
{
const UInventoryDragDropOperation* Payload = Cast<UInventoryDragDropOperation>(InOperation);
if (!Payload || SlotIndex == INDEX_NONE)
{
return Super::NativeOnDrop(InGeometry, InDragDropEvent, InOperation);
}
// TransferSlot diffuse OnInventoryChanged des DEUX cotes, donc les grilles
// se rafraichissent seules. Elle gere indifferemment le meme inventaire et
// deux inventaires distincts : glisser vers un coffre, vers la barre rapide
// ou vers la case d'a cote emprunte exactement le meme chemin.
return UInventoryComponent::TransferSlot(
Payload->SourceInventory.Get(), Payload->SourceSlotIndex,
OwningInventory.Get(), SlotIndex,
Payload->Quantity);
}