Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(input): add interaction to assembler (#89)
  • Loading branch information
Xenira committed Jan 26, 2024
1 parent 54fb1ad commit f235fc0
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 4 deletions.
47 changes: 44 additions & 3 deletions plugin/src/input/ui/InteractableUi.cs
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using TechtonicaVR.UI;
Expand Down Expand Up @@ -94,13 +95,19 @@ public UiRaycastHit Raycast(Ray ray)

public Interactable getInteractable(Vector2 point)
{
Logger.LogDebug($"getInteractable {transform.gameObject.name} {point}");
return interactable.Where(i => i.isHit(point)).FirstOrDefault();
// Logger.LogDebug($"getInteractable {transform.gameObject.name} {point}");
return interactable.Where(i => i.isHit(point) && i.gameObject.activeInHierarchy).FirstOrDefault();
}

public void RecalculateInteractablePositions()
{
interactable.ForEach(i => i.recalculate());
}

public void OnEnter()
{
Logger.LogDebug($"OnEnter {transform.gameObject.name}");
RecalculateInteractablePositions();
OnEnterEvent?.Invoke();
}
public void OnExit()
Expand All @@ -117,6 +124,17 @@ private Plane getUiPlane()
{
return new Plane(transform.forward, transform.position);
}

protected Rect getRect(RectTransform rectTransform)
{
var rect = rectTransform.rect;

var relativeLocalPosition = ObjectPosition.addLocalPositions(rectTransform, this.rectTransform);

rect.x += relativeLocalPosition.x;
rect.y += relativeLocalPosition.y;
return rect;
}
}

public delegate void UiEnterEvent();
Expand All @@ -138,6 +156,7 @@ public class InteractableBuilder

private InteractableIsHitCallback isHitCallback;
private InteractableGetObjectCallback getObjectCallback;
private Func<Rect> recalculateCallback;

public InteractableBuilder(InteractableUi ui, Rect rect, GameObject gameObject)
{
Expand All @@ -148,7 +167,20 @@ public InteractableBuilder(InteractableUi ui, Rect rect, GameObject gameObject)

public Interactable build()
{
return new Interactable(ui, rect, gameObject, isHitCallback, getObjectCallback, onClickEvent, onDragEvent, onDropEvent, onCancelDragEvent, onAcceptsDropEvent, onReceiveDropEvent, onHoverEnterEvent, onHoverExitEvent);
var interactable = new Interactable(ui, rect, gameObject, isHitCallback, getObjectCallback, onClickEvent, onDragEvent, onDropEvent, onCancelDragEvent, onAcceptsDropEvent, onReceiveDropEvent, onHoverEnterEvent, onHoverExitEvent);

if (recalculateCallback != null)
{
interactable.recalculateCallback = recalculateCallback;
}

return interactable;
}

public InteractableBuilder withRecalculate(Func<Rect> recalculateCallback)
{
this.recalculateCallback = recalculateCallback;
return this;
}

public InteractableBuilder withIsHit(InteractableIsHitCallback isHitCallback)
Expand Down Expand Up @@ -210,6 +242,7 @@ public class Interactable

public InteractableIsHitCallback IsHitCallback;
public InteractableGetObjectCallback GetObjectCallback;
public Func<Rect> recalculateCallback;

// Clickable, Draggable and Drop Target
internal Interactable(InteractableUi ui, Rect rect, GameObject gameObject, InteractableIsHitCallback isHitCallback, InteractableGetObjectCallback getObjectCallback, InteractableClickEvent onClick, InteractableDragEvent onDrag, InteractableDropEvent onDrop, InteractableCancelDragEvent onCancelDrag, InteractableAcceptsDropEvent onAcceptsDrop, InteractableReceiveDropEvent onReceiveDrop, InteractableHoverEnterEvent onHoverEnter, InteractableHoverExitEvent onHoverExit)
Expand All @@ -230,6 +263,14 @@ internal Interactable(InteractableUi ui, Rect rect, GameObject gameObject, Inter
GetObjectCallback = getObjectCallback;
}

public void recalculate()
{
if (recalculateCallback != null)
{
rect = recalculateCallback();
}
}

public bool isHit(Vector2 point)
{
if (IsHitCallback != null)
Expand Down
67 changes: 67 additions & 0 deletions plugin/src/input/ui/machine/AssemblerInteractableUi.cs
@@ -0,0 +1,67 @@
using System.Linq;
using TechtonicaVR.Util;
using UnityEngine;

namespace TechtonicaVR.Input.Ui.Machine;

public class AssemblerInteractableUi : InventoryInteractableUI
{
private static PluginLogger Logger = PluginLogger.GetLogger<AssemblerInteractableUi>();

public AssemblerInteractableUi(GameObject gameObject) : base(gameObject)
{
zIndex = 0.001f;

interactable = GameObjectFinder.FindChildObjectByName("Container", gameObject).GetComponentsInChildren<InventoryResourceSlotUI>().Select(getInteractable).ToList();

Logger.LogDebug($"Interactable: {rectTransform.rect}");
}



protected override void init()
{
}

private Interactable getInteractable(InventoryResourceSlotUI slot)
{
var rectTransform = slot.GetComponent<RectTransform>();
var rect = getRect(rectTransform);
Logger.LogDebug($"Slot rect: {slot} {rect} {rectTransform.localPosition}");

return new InteractableBuilder(this, rect, rectTransform.gameObject)
.withRecalculate(() => getRect(rectTransform))
.withDrag(() => draggedResourceInfo ?? slot.resourceType,
(ui) => onDrag(slot),
(ui, source, target) => onDrop(ui, target, slot),
(ui) => onCancelDrag(slot))
.withDrop(onAcceptsDrop, (ui, source) => onReceiveDrop(source, slot))
.withHoverEnter((ui) => onHoverEnter(slot))
.withHoverExit((ui) => onHoverExit(slot))
.build();
}
}

public class AssemblerRecipeSelectInteractableUi : InventoryInteractableUI
{
public AssemblerRecipeSelectInteractableUi(GameObject gameObject) : base(gameObject)
{
var recipeSelector = gameObject.GetComponentInChildren<InventoryResourceSlotUI>();
interactable = [
new InteractableBuilder(this, getRect(recipeSelector.GetComponent<RectTransform>()), recipeSelector.gameObject)
.withClick((ui) => onClick(recipeSelector))
.withHoverEnter((ui) => onHoverEnter(recipeSelector))
.withHoverExit((ui) => onHoverExit(recipeSelector))
.build()
];
}

protected override void init()
{
}

private void onClick(InventoryResourceSlotUI recipeSelector)
{
recipeSelector.mouseLeftClickCallback.Invoke();
}
}
15 changes: 14 additions & 1 deletion plugin/src/ui/patch/UiMenuPatch.cs
Expand Up @@ -49,7 +49,7 @@ public static void StartPostfix(UIMenu __instance)
canvas.transform.localScale = ModConfig.menuScale.Value;
}

addInteractableUi(__instance);
AsyncGameObject.Instance.timeoutFrames(() => addInteractableUi(__instance), 1);
disableButtonPrompts(__instance);
}

Expand Down Expand Up @@ -87,6 +87,19 @@ private static void addInteractableUi(UIMenu instance)
menu = new UIMenuWrapper(instance)
};
}
else if (instance is AssemblerUI)
{
var container = GameObjectFinder.FindChildObjectByName("Container", instance.gameObject);
var sidebar = GameObjectFinder.FindChildObjectByName("visible sidebar area", instance.gameObject);
new AssemblerInteractableUi(container)
{
menu = new UIMenuWrapper(instance)
};
new AssemblerRecipeSelectInteractableUi(sidebar)
{
menu = new UIMenuWrapper(instance)
};
}
}

private static void disableButtonPrompts(UIMenu __instance)
Expand Down
78 changes: 78 additions & 0 deletions plugin/src/util/Async.cs
@@ -0,0 +1,78 @@
using System;
using System.Collections;
using UnityEngine;

namespace TechtonicaVR.Util;

public class Async
{
public static IEnumerator Timeout(Action callback, float seconds)
{
yield return new WaitForSeconds(seconds);
callback();
}

public static IEnumerator TimeoutFrames(Action callback, int frames)
{
for (int i = 0; i < frames; i++)
{
yield return new WaitForEndOfFrame();
}
callback();
}

public static IEnumerator Interval(Action callback, float seconds, float startInSeconds)
{
yield return new WaitForSeconds(startInSeconds < 0 ? seconds : startInSeconds);
while (true)
{
callback();
yield return new WaitForSeconds(seconds);
}
}
}

public class AsyncGameObject : MonoBehaviour
{
private static PluginLogger Logger = PluginLogger.GetLogger<AsyncGameObject>();
private static AsyncGameObject instance;

public static AsyncGameObject Instance
{
get
{
if (instance == null)
{
instance = new GameObject(nameof(AsyncGameObject)).AddComponent<AsyncGameObject>();
}
return instance;
}
}

void Awake()
{
if (instance != null)
{
Logger.LogError("AsyncGameObject already exists, destroying this one");
Destroy(this);
return;
}
instance = this;
DontDestroyOnLoad(gameObject);
}

public void timeout(Action callback, float seconds)
{
StartCoroutine(Async.Timeout(callback, seconds));
}

public void timeoutFrames(Action callback, int frames)
{
StartCoroutine(Async.TimeoutFrames(callback, frames));
}

public void interval(Action callback, float seconds, float startInSeconds)
{
StartCoroutine(Async.Interval(callback, seconds, startInSeconds));
}
}

0 comments on commit f235fc0

Please sign in to comment.