Skip to content

Commit

Permalink
fix(input): fix scroll views (#118)
Browse files Browse the repository at this point in the history
  • Loading branch information
Xenira authored Jan 31, 2024
1 parent a1e753c commit 2b6c228
Show file tree
Hide file tree
Showing 15 changed files with 234 additions and 74 deletions.
11 changes: 10 additions & 1 deletion README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ image:https://img.shields.io/github/license/{repo}["License", link="https://gith

This is a mod for the game https://store.steampowered.com/app/1457320/Techtonica/[Techtonica] that adds VR support.

CAUTION: This mod is still in early development and *will* contain bugs. Use at your own risk. Only tested with the Valve Index / Pimax Crystal and Knuckles controllers.
****
I spent way over 100 hours of my free time on this mod.
If you enjoy it, please consider supporting me on https://liberapay.com/rip3.141[Liberapay] ❤️
image:https://liberapay.com/assets/widgets/donate.svg["Donate using Liberapay", link="https://liberapay.com/rip3.141"]
****

ifdef::env-github[]
toc::[]
Expand Down Expand Up @@ -180,6 +185,10 @@ If you encounter any issues while using this mod, please check the BepInEx conso
[qanda]
Why is my framerate locked to 60fps?::
The game is locked to x fps when running in Windowed mode. This is based on the refresh rate of your monitor. To unlock the framerate, switch to fullscreen mode. (For now)
I am experiencing periodic stuttering / freezes. What can I do?::
This is most likely caused by autosave. Try setting the autosave interval to a higher value using the https://thunderstore.io/c/techtonica/p/UnFoundBug/AutoSaveConfig/[AutoSaveConfig] mod.
The games performance is bad. What can I do?::
Try lowering the graphics settings. VR is very demanding and the game is not optimized for VR. While I am working on improving the performance, there is only so much I can do.
Does this mod work with Gamepass?::
Yes, the mod needs to be installed in `Gamepass/Techtonica/Content`.
// AI generated below :P
Expand Down
12 changes: 11 additions & 1 deletion libs/StreamingAssets/SteamVR/bindings_knuckles.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
"parameters": {
"force_hold_threshold": "0.02",
"force_release_threshold": "0.01",
"value_hold_threshold": "1.05"
"value_hold_threshold": "1.3"
},
"inputs": {
"grab": {
Expand Down Expand Up @@ -432,6 +432,16 @@
"output": "/actions/ui/in/click"
}
}
},
{
"path": "/user/hand/left/input/trigger",
"mode": "button",
"parameters": {},
"inputs": {
"click": {
"output": "/actions/ui/in/click"
}
}
}
],
"skeleton": []
Expand Down
10 changes: 10 additions & 0 deletions libs/StreamingAssets/SteamVR/bindings_oculus_touch.json
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,16 @@
"output": "/actions/ui/in/click"
}
}
},
{
"path": "/user/hand/left/input/trigger",
"mode": "button",
"parameters": {},
"inputs": {
"click": {
"output": "/actions/ui/in/click"
}
}
}
],
"skeleton": []
Expand Down
2 changes: 1 addition & 1 deletion plugin/src/input/SteamvrInputMapper.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using TechtonicaVR.Util;
using UnityEngine;
using Valve.VR;
Expand All @@ -11,7 +12,6 @@ public static class SteamVRInputMapper
public static Vector2 MoveAxes { get; private set; }
public static Vector2 UIAxesPrimary { get; private set; }
public static Vector2 UIAxesSecondary { get; private set; }

public static float TurnAxis { get; private set; }

public static GameObject leftHandObject;
Expand Down
114 changes: 80 additions & 34 deletions plugin/src/input/ui/InteractableUi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,27 @@ public abstract class InteractableUi

public Transform transform;
public float zIndex = 0;
protected ScrollRect scrollRect;

protected Rect? viewportRect;
private ScrollRect _scrollRect;
private ScrollRect scrollRect
{
get => _scrollRect;
set
{
_scrollRect = value;
if (value != null)
{
value.onValueChanged.AddListener((v) => refresh());
viewportRect = getRect(value.viewport);
}
else
{
viewportRect = null;
}
}
}

protected RectTransform rectTransform;
private Canvas canvas;

Expand All @@ -34,6 +54,8 @@ public InteractableUi(GameObject gameObject)
}
canvas = gameObject.GetComponentInParent<Canvas>();

scrollRect = gameObject.GetComponentInChildren<ScrollRect>();

LaserPointer.interactables.Add(this);
}

Expand Down Expand Up @@ -61,39 +83,49 @@ public UiRaycastHit Raycast(Ray ray)
return null;
}

if (scrollRect != null)
if (viewportRect?.Contains(localPoint) == true)
{
if (scrollRect.verticalScrollbar)
{
if (localPoint.x < localRect.x + 12)
{
scrollRect.verticalScrollbar.value += scrollRect.verticalScrollbar.stepSize;
}
else if (localPoint.x > localRect.width - 12)
{
scrollRect.verticalScrollbar.value -= scrollRect.verticalScrollbar.stepSize;
}
}
if (scrollRect.horizontalScrollbar)
{
if (localPoint.y < localRect.y + 12)
{
scrollRect.horizontalScrollbar.value -= scrollRect.horizontalScrollbar.stepSize;
}
else if (localPoint.y > localRect.height - 12)
{
scrollRect.horizontalScrollbar.value += scrollRect.horizontalScrollbar.stepSize;
}
}
Scroll(localPoint);
}

var scrollOffset = scrollRect?.m_PrevPosition ?? Vector2.zero;
return new UiRaycastHit(this, distance + zIndex, point, localPoint + scrollOffset);
return new UiRaycastHit(this, distance + zIndex, point, localPoint);
}

return null;
}

private void Scroll(Vector2 localPoint)
{
var viewportPoint = new Vector2(localPoint.x - viewportRect.Value.x, localPoint.y - viewportRect.Value.y);
var yPercent = viewportPoint.y / viewportRect.Value.height;

var scrollSpeed = 0.125f;
var deadzone = 0.35f;
float scrollAmount;
if (yPercent < 0.5f - deadzone)
{
if (scrollRect.verticalNormalizedPosition <= 0f)
{
return;
}
scrollAmount = (yPercent - (0.5f - deadzone)) * scrollSpeed;
}
else if (yPercent > 0.5f + deadzone)
{
if (scrollRect.verticalNormalizedPosition >= 1f)
{
return;
}
scrollAmount = (yPercent - (0.5f + deadzone)) * scrollSpeed;
}
else
{
return;
}

scrollRect.verticalNormalizedPosition += scrollAmount;
}

public Interactable getInteractable(Vector2 point)
{
// Logger.LogDebug($"getInteractable {transform.gameObject.name} {point}");
Expand All @@ -111,6 +143,17 @@ public bool rebuildInteractables()
return true;
}

public void refresh()
{
AsyncGameObject.TimeoutFrames(() =>
{
if (!rebuildInteractables())
{
recalculateInteractablePositions();
}
}, 1);
}

public void recalculateInteractablePositions()
{
interactable.ForEach(i => i.recalculate());
Expand All @@ -119,13 +162,16 @@ public void recalculateInteractablePositions()
public void OnEnter()
{
Logger.LogDebug($"OnEnter {transform.gameObject.name}");
AsyncGameObject.Instance.timeoutFrames(() =>
{
if (!rebuildInteractables())
{
recalculateInteractablePositions();
}
}, 1);

refresh();
AsyncGameObject.TimeoutFrames(() =>
{
if (scrollRect != null)
{
viewportRect = getRect(scrollRect.viewport);
}
}, 1);

OnEnterEvent?.Invoke();
}
public void OnExit()
Expand All @@ -151,7 +197,7 @@ protected Rect getRect(RectTransform rectTransform)

rect.x += relativeLocalPosition.x;
rect.y += relativeLocalPosition.y;
Logger.LogDebug($"getRect {rectTransform.gameObject.name} {rect} {relativeLocalPosition}");
Logger.LogTrace($"getRect {rectTransform.gameObject.name} {rect} {relativeLocalPosition}");
return rect;
}
}
Expand Down
16 changes: 5 additions & 11 deletions plugin/src/input/ui/InventoryInteractableUi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class InventoryInteractableUI : InteractableUi
private static PluginLogger Logger = PluginLogger.GetLogger<InventoryInteractableUI>();
protected ResourceInfo draggedResourceInfo;
private int draggedResourceCount;
private Transform viewport;
private InventoryGridUI inv;

public InventoryInteractableUI(GameObject gameObject) : base(gameObject)
{
Expand All @@ -20,23 +20,17 @@ public InventoryInteractableUI(GameObject gameObject) : base(gameObject)

protected virtual void init()
{
var inv = transform.gameObject.GetComponentInParent<InventoryGridUI>();
interactable = inv.ui.slots.Select(getInteractable).ToList();
scrollRect = inv.GetComponent<ScrollRect>();
inv = transform.gameObject.GetComponentInParent<InventoryGridUI>();
getInteractables = () => inv.ui.slots.Select(getInteractable).ToList();
}

private Interactable getInteractable(InventoryResourceSlotUI slot, int index)
{
var rectTransform = slot.GetComponent<RectTransform>();
var rect = rectTransform.rect;
viewport = rectTransform.gameObject.GetComponentInParent<CanvasRenderer>().transform;

rect.x += rectTransform.localPosition.x;
rect.y += rectTransform.localPosition.y;
var rect = getRect(rectTransform);

return new InteractableBuilder(this, rect, rectTransform.gameObject)
// .withClick((ui) => onClick(uiSlot))
.withIsHit((point) => rect.Contains(point - rectTransform.parent.parent.localPosition.ToVector2() - rectTransform.parent.parent.parent.localPosition.ToVector2()))
.withRecalculate(() => getRect(rectTransform))
.withDrag(() => draggedResourceInfo ?? slot.resourceType,
(ui) => onDrag(slot),
(ui, source, target) => onDrop(ui, target, slot),
Expand Down
7 changes: 2 additions & 5 deletions plugin/src/input/ui/machine/PowerGeneratorInteractableUi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,11 @@ public PowerGeneratorInteractableUi(GameObject gameObject) : base(gameObject)

var button = GameObjectFinder.FindChildObjectByName("Controller Activate Crank CG", gameObject);
var rectTransform = button.GetComponent<RectTransform>();
var rect = rectTransform.rect;

Logger.LogDebug($"Button rect: {rect} {rectTransform.localPosition}");
rect.x += rectTransform.localPosition.x;
rect.y = 260; // TODO: Find a way to get this value dynamically
var rect = getRect(rectTransform);

interactable = [
new InteractableBuilder(this, rect, button)
.withRecalculate(() => getRect(rectTransform))
.withClick((ui) => onClick())
.build()
];
Expand Down
12 changes: 2 additions & 10 deletions plugin/src/input/ui/machine/ThresherInteractableUi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,10 @@ protected override void init()
private Interactable getInteractable(InventoryResourceSlotUI slot)
{
var rectTransform = slot.GetComponent<RectTransform>();
var rect = rectTransform.rect;

var originalParent = rectTransform.parent;
rectTransform.parent = this.rectTransform;
var relativeLocalPosition = rectTransform.localPosition;
rectTransform.parent = originalParent;

rect.x += relativeLocalPosition.x;
rect.y += relativeLocalPosition.y;
Logger.LogDebug($"Slot rect: {slot} {rect} {rectTransform.localPosition}");
var rect = getRect(rectTransform);

return new InteractableBuilder(this, rect, rectTransform.gameObject)
.withRecalculate(() => getRect(rectTransform))
.withDrag(() => draggedResourceInfo ?? slot.resourceType,
(ui) => onDrag(slot),
(ui, source, target) => onDrop(ui, target, slot),
Expand Down
22 changes: 22 additions & 0 deletions plugin/src/ui/patch/InventoryGridUIPatch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using HarmonyLib;

namespace TechtonicaVR.UI.Patch;

[Harmony]
public class InventoryGridUIPatch
{
[HarmonyPrefix]
[HarmonyPatch(typeof(BaseResourceGridUI.SlotsSetup), nameof(BaseResourceGridUI.SlotsSetup.shiftCount), MethodType.Getter)]
public static void totalNumRowsPrefix(BaseResourceGridUI.SlotsSetup __instance)
{
__instance._shiftCount = (__instance.rotateSlots ? (__instance.scrollContent.anchoredPosition.y / __instance.spacing.y).Abs().FloorToInt() : 0);
}

[HarmonyPrefix]
[HarmonyPatch(typeof(BaseResourceGridUI), nameof(BaseResourceGridUI.ResetToTop))]
public static bool ResetToTopPrefix(BaseResourceGridUI __instance)
{
__instance.slotsSetup.ResetScrollToTop();
return false;
}
}
4 changes: 2 additions & 2 deletions plugin/src/ui/patch/UiMenuPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static void StartPostfix(UIMenu __instance)
canvas.transform.localScale = ModConfig.menuScale.Value;
}

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

Expand Down Expand Up @@ -248,7 +248,7 @@ public static void RecipePickerUIStartPostfix(RecipePickerUI __instance)
canvas.transform.localPosition = Vector3.zero;
canvas.transform.localScale = ModConfig.menuScale.Value;

AsyncGameObject.Instance.timeoutFrames(() =>
AsyncGameObject.TimeoutFrames(() =>
{
var container = GameObjectFinder.FindChildObjectByName("Container", __instance.gameObject);
new RecipePickerInteractableUi(container)
Expand Down
Loading

0 comments on commit 2b6c228

Please sign in to comment.