Initial multiselect checkpoint, still long way to go

This commit is contained in:
Tyfon
2024-06-14 14:08:08 -07:00
parent 4c7e465098
commit 0bf508df1f
6 changed files with 595 additions and 0 deletions

87
DrawMultiSelect.cs Normal file
View File

@@ -0,0 +1,87 @@
using EFT.UI;
using EFT.UI.DragAndDrop;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace UIFixes
{
public class DrawMultiSelect : MonoBehaviour
{
Texture2D selectTexture;
Vector3 selectOrigin;
Vector3 selectEnd;
bool drawing;
public void Start()
{
selectTexture = new Texture2D(1, 1);
selectTexture.SetPixel(0, 0, new Color(1f, 1f, 1f, 0.8f));
selectTexture.Apply();
}
public void Update()
{
if (Input.GetKeyDown(KeyCode.Mouse0) && ItemUiContext.Instance.R().ItemContext == null)
{
selectOrigin = Input.mousePosition;
drawing = true;
}
if (drawing)
{
selectEnd = Input.mousePosition;
Rect selectRect = new(selectOrigin.x, selectOrigin.y, selectEnd.x - selectOrigin.x, selectEnd.y - selectOrigin.y);
foreach (GridItemView gridItemView in GetComponentsInChildren<GridItemView>())
{
RectTransform itemTransform = gridItemView.GetComponent<RectTransform>();
Rect screenRect = new((Vector2)itemTransform.position + itemTransform.rect.position, itemTransform.rect.size);
if (selectRect.Overlaps(screenRect, true))
{
MultiSelect.Select(gridItemView);
}
else
{
MultiSelect.Deselect(gridItemView);
}
}
}
if (drawing && !Input.GetKey(KeyCode.Mouse0))
{
drawing = false;
}
}
public void OnGUI()
{
if (drawing)
{
// Invert Y because GUI has upper-left origin
Rect area = new(selectOrigin.x, Screen.height - selectOrigin.y, selectEnd.x - selectOrigin.x, selectOrigin.y - selectEnd.y);
Rect lineArea = area;
lineArea.height = 1; // Top
GUI.DrawTexture(lineArea, selectTexture);
lineArea.y = area.yMax - 1; // Bottom
GUI.DrawTexture(lineArea, selectTexture);
lineArea = area;
lineArea.width = 1; // Left
GUI.DrawTexture(lineArea, selectTexture);
lineArea.x = area.xMax - 1; // Right
GUI.DrawTexture(lineArea, selectTexture);
}
}
}
}

172
MultiSelect.cs Normal file
View File

@@ -0,0 +1,172 @@
using EFT.InventoryLogic;
using EFT.UI.DragAndDrop;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;
namespace UIFixes
{
public class MultiSelect
{
private static GameObject SelectedMarkTemplate;
private static GameObject SelectedBackgroundTemplate;
private static readonly HashSet<GridItemView> SelectedItemViews = [];
private static readonly List<ItemContextClass> SelectedItemContexts = [];
public static void Initialize()
{
// Grab the selection objects from ragfair as templates
RagfairNewOfferItemView ragfairNewOfferItemView = ItemViewFactory.CreateFromPool<RagfairNewOfferItemView>("ragfair_layout");
SelectedMarkTemplate = UnityEngine.Object.Instantiate(ragfairNewOfferItemView.R().SelectedMark, null, false);
SelectedBackgroundTemplate = UnityEngine.Object.Instantiate(ragfairNewOfferItemView.R().SelectedBackground, null, false);
ragfairNewOfferItemView.ReturnToPool();
}
public static void Toggle(GridItemView itemView)
{
if (!itemView.IsInteractable)
{
return;
}
if (SelectedItemViews.Contains(itemView))
{
Deselect(itemView);
}
else
{
Select(itemView);
}
}
public static void Clear()
{
// ToList() because we'll be modifying the collection
foreach (GridItemView itemView in SelectedItemViews.ToList())
{
Deselect(itemView);
}
}
public static void Select(GridItemView itemView)
{
if (itemView.IsInteractable && SelectedItemViews.Add(itemView))
{
ShowSelection(itemView);
}
}
public static void Deselect(GridItemView itemView)
{
if (SelectedItemViews.Remove(itemView))
{
HideSelection(itemView);
}
}
public static IEnumerable<ItemView> ItemViews
{
get { return SelectedItemViews; }
}
public static IEnumerable<ItemContextClass> ItemContexts
{
get { return SelectedItemContexts; }
}
public static int Count
{
get { return SelectedItemViews.Count; }
}
public static bool Contains(GridItemView itemView)
{
return SelectedItemViews.Contains(itemView);
}
public static bool Active
{
get { return SelectedItemViews.Any(); }
}
public static bool IsSelected(GridItemView itemView)
{
return SelectedItemViews.Contains(itemView);
}
public static void BeginDrag()
{
foreach (ItemView itemView in SelectedItemViews)
{
SelectedItemContexts.Add(new ItemContextClass(itemView.ItemContext, itemView.ItemRotation));
}
}
public static void EndDrag()
{
foreach(ItemContextClass itemContext in SelectedItemContexts)
{
itemContext.Dispose();
}
SelectedItemContexts.Clear();
}
public static void ShowDragCount(DraggedItemView draggedItemView)
{
if (Count > 1)
{
GameObject textOverlay = new("MultiSelectText", [typeof(RectTransform), typeof(TextMeshProUGUI)]);
textOverlay.transform.parent = draggedItemView.transform;
textOverlay.transform.SetAsLastSibling();
textOverlay.SetActive(true);
RectTransform overlayRect = textOverlay.GetComponent<RectTransform>();
overlayRect.anchorMin = Vector2.zero;
overlayRect.anchorMax = Vector2.one;
overlayRect.anchoredPosition = new Vector2(0.5f, 0.5f);
TextMeshProUGUI text = textOverlay.GetComponent<TextMeshProUGUI>();
text.text = MultiSelect.Count.ToString();
text.fontSize = 36;
text.alignment = TextAlignmentOptions.Baseline;
}
}
private static void ShowSelection(GridItemView itemView)
{
GameObject selectedMark = itemView.transform.Find("SelectedMark")?.gameObject;
if (selectedMark == null)
{
selectedMark = UnityEngine.Object.Instantiate(SelectedMarkTemplate, itemView.transform, false);
selectedMark.name = "SelectedMark";
}
selectedMark.SetActive(true);
GameObject selectedBackground = itemView.transform.Find("SelectedBackground")?.gameObject;
if (selectedBackground == null)
{
selectedBackground = UnityEngine.Object.Instantiate(SelectedBackgroundTemplate, itemView.transform, false);
selectedBackground.transform.SetAsFirstSibling();
selectedBackground.name = "SelectedBackground";
}
selectedBackground.SetActive(true);
}
private static void HideSelection(GridItemView itemView)
{
GameObject selectedMark = itemView.transform.Find("SelectedMark")?.gameObject;
GameObject selectedBackground = itemView.transform.Find("SelectedBackground")?.gameObject;
selectedMark?.SetActive(false);
selectedBackground?.SetActive(false);
}
}
}

View File

@@ -0,0 +1,313 @@
using Aki.Reflection.Patching;
using Comfort.Common;
using EFT.UI;
using EFT.UI.DragAndDrop;
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UIFixes
{
public static class MultiSelectPatches
{
// Used to prevent infinite recursion of CanAccept/AcceptItem
private static bool InPatch = false;
public static void Enable()
{
new InitializePatch().Enable();
new SelectPatch().Enable();
new DeselectOnOtherMouseDown().Enable();
new DeselectOnMovePatch().Enable();
new BeginDragPatch().Enable();
new EndDragPatch().Enable();
new GridViewCanAcceptPatch().Enable();
new SlotViewCanAcceptPatch().Enable();
new SlotViewAcceptItemPatch().Enable();
}
public class InitializePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(CommonUI), nameof(CommonUI.Awake));
}
[PatchPostfix]
public static void Postfix(CommonUI __instance)
{
MultiSelect.Initialize();
__instance.InventoryScreen.GetOrAddComponent<DrawMultiSelect>();
//__instance.TransferItemsInRaidScreen.GetOrAddComponent<DrawMultiSelect>();
//__instance.TransferItemsScreen.GetOrAddComponent<DrawMultiSelect>();
}
}
public class SelectPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(GridItemView), nameof(GridItemView.OnClick));
}
[PatchPostfix]
public static void Postfix(GridItemView __instance, PointerEventData.InputButton button)
{
if (button == PointerEventData.InputButton.Left && (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)))
{
MultiSelect.Toggle(__instance);
return;
}
if (button == PointerEventData.InputButton.Left)// && !MultiSelect.IsSelected(__instance))
{
MultiSelect.Clear();
}
}
}
public class DeselectOnOtherMouseDown : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemView), nameof(ItemView.OnPointerDown));
}
[PatchPostfix]
public static void Postfix(ItemView __instance, PointerEventData eventData)
{
if (eventData.button == PointerEventData.InputButton.Left && (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)))
{
// This will be shift-click, let it cook
return;
}
if (__instance is not GridItemView gridItemView || !MultiSelect.IsSelected(gridItemView))
{
MultiSelect.Clear();
}
}
}
public class DeselectOnMovePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemView), nameof(ItemView.Kill));
}
[PatchPostfix]
public static void Postfix(ItemView __instance)
{
if (__instance is GridItemView gridItemView)
{
MultiSelect.Deselect(gridItemView);
}
}
}
public class BeginDragPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemView), nameof(ItemView.OnBeginDrag));
}
[PatchPrefix]
public static void Prefix()
{
MultiSelect.BeginDrag();
}
[PatchPostfix]
public static void Postfix(ItemView __instance)
{
MultiSelect.ShowDragCount(__instance.DraggedItemView);
}
}
public class EndDragPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemView), nameof(ItemView.OnEndDrag));
}
[PatchPostfix]
public static void Postfix()
{
MultiSelect.EndDrag();
}
}
public class GridViewCanAcceptPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(GridView), nameof(GridView.CanAccept));
}
[PatchPrefix]
public static bool Prefix(GridView __instance, ItemContextAbstractClass targetItemContext, ref GStruct413 operation, ref bool __result)
{
if (InPatch || !MultiSelect.Active)
{
return true;
}
operation = default;
__result = false;
return false;
/* InPatch = true;
foreach (ItemContextClass itemContext in MultiSelect.ItemContexts)
{
__result = __instance.CanAccept(itemContext, targetItemContext, out operation);
if (!__result)
{
break;
}
}
InPatch = false;
return false;*/
}
}
public class SlotViewCanAcceptPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(SlotView), nameof(SlotView.CanAccept));
}
[PatchPrefix]
public static bool Prefix(SlotView __instance, ItemContextAbstractClass targetItemContext, ref GStruct413 operation, ref bool __result)
{
if (InPatch || !MultiSelect.Active)
{
return true;
}
operation = default;
__result = false;
InPatch = true;
foreach (ItemContextClass itemContext in MultiSelect.ItemContexts)
{
__result = __instance.CanAccept(itemContext, targetItemContext, out operation);
if (!__result)
{
break;
}
}
InPatch = false;
return false;
}
}
public class SlotViewAcceptItemPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(SlotView), nameof(SlotView.AcceptItem));
}
[PatchPrefix]
public static bool Prefix(SlotView __instance, ItemContextAbstractClass targetItemContext, ref Task __result)
{
if (InPatch || !MultiSelect.Active)
{
return true;
}
InPatch = true;
/* __result = Task.CompletedTask;
foreach (ItemContextClass itemContext in MultiSelect.ItemContexts.ToList())
{
__result = __result.ContinueWith(_ => __instance.AcceptItem(itemContext, targetItemContext));
}*/
var serializer = __instance.GetOrAddComponent<TaskSerializer>();
__result = serializer.Initialize(MultiSelect.ItemContexts, itemContext => __instance.AcceptItem(itemContext, targetItemContext));
__result.ContinueWith(_ => { InPatch = false; });
return false;
}
}
public class TaskSerializer : MonoBehaviour
{
private Func<ItemContextClass, Task> func;
private Queue<ItemContextClass> itemContexts;
private Task currentTask;
private TaskCompletionSource totalTask;
public Task Initialize(IEnumerable<ItemContextClass> itemContexts, Func<ItemContextClass, Task> func)
{
this.itemContexts = new(itemContexts);
this.func = func;
currentTask = Task.CompletedTask;
Update();
totalTask = new TaskCompletionSource();
return totalTask.Task;
}
public void Update()
{
if (!currentTask.IsCompleted)
{
return;
}
if (itemContexts.Any())
{
currentTask = func(itemContexts.Dequeue());
}
else
{
totalTask.Complete();
func = null;
Destroy(this);
}
}
}
/*public class GridViewAcceptItemPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(GridView), nameof(GridView.AcceptItem));
}
[PatchPrefix]
public static async bool Prefix(GridView __instance, ItemContextAbstractClass targetItemContext, ref Task __result)
{
if (InPatch || !MultiSelectContext.Instance.Any())
{
return true;
}
InPatch = true;
foreach (ItemContextClass itemContext in MultiSelectContext.Instance.SelectedDragContexts)
{
await __instance.AcceptItem(itemContext, targetItemContext);
}
InPatch = false;
return false;
}
}*/
}
}

View File

@@ -49,6 +49,7 @@ namespace UIFixes
ContextMenuShortcutPatches.Enable();
new OpenSortingTablePatch().Enable();
LoadAmmoInRaidPatches.Enable();
MultiSelectPatches.Enable();
}
public static bool InRaid()

19
R.cs
View File

@@ -58,6 +58,7 @@ namespace UIFixes
GridSortPanel.InitTypes();
RepairStrategy.InitTypes();
ContextMenuHelper.InitTypes();
RagfairNewOfferItemView.InitTypes();
}
public abstract class Wrapper(object value)
@@ -674,6 +675,23 @@ namespace UIFixes
public InsuranceCompanyClass InsuranceCompany { get { return (InsuranceCompanyClass)InsuranceCompanyField.GetValue(Value); } }
}
public class RagfairNewOfferItemView(object value) : Wrapper(value)
{
public static Type Type { get; private set; }
private static FieldInfo SelectedMarkField;
private static FieldInfo SelectedBackgroundField;
public static void InitTypes()
{
Type = typeof(EFT.UI.DragAndDrop.RagfairNewOfferItemView);
SelectedMarkField = AccessTools.Field(Type, "_selectedMark");
SelectedBackgroundField = AccessTools.Field(Type, "_selectedBackground");
}
public GameObject SelectedMark { get { return (GameObject)SelectedMarkField.GetValue(Value); } }
public GameObject SelectedBackground { get { return (GameObject)SelectedBackgroundField.GetValue(Value); } }
}
}
public static class RExtentensions
@@ -700,5 +718,6 @@ namespace UIFixes
public static R.GridSortPanel R(this GridSortPanel value) => new(value);
public static R.RepairerParametersPanel R(this RepairerParametersPanel value) => new(value);
public static R.MessageWindow R(this MessageWindow value) => new(value);
public static R.RagfairNewOfferItemView R(this RagfairNewOfferItemView value) => new(value);
}
}

View File

@@ -57,6 +57,9 @@
<Reference Include="UnityEngine.CoreModule">
<HintPath>$(PathToSPT)\EscapeFromTarkov_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.IMGUIModule">
<HintPath>$(PathToSPT)\EscapeFromTarkov_Data\Managed\UnityEngine.IMGUIModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.InputLegacyModule">
<HintPath>$(PathToSPT)\EscapeFromTarkov_Data\Managed\UnityEngine.InputLegacyModule.dll</HintPath>
</Reference>