-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBepinexModCompatibilityLayerButtonGenerator.cs
More file actions
148 lines (132 loc) · 7.34 KB
/
BepinexModCompatibilityLayerButtonGenerator.cs
File metadata and controls
148 lines (132 loc) · 7.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace BepinexModCompatibilityLayer {
/// <summary>
/// This class is used to avoid overlap between button positioned at a static location.
/// It's recommended to know how Tuples work for using this.
/// </summary>
public class BepinexModCompatibilityLayerButtonGenerator {
internal BepinexModCompatibilityLayerButtonGenerator() {
_ignoredRects = new List<Rect>();
}
private static readonly List<(Rect buttonFrame, int signature)> OccupiedLocations = new List<(Rect buttonFrame, int signature)>();
internal List<Rect> IgnoredRects => _ignoredRects.ToList();
private readonly List<Rect> _ignoredRects;
private static int _signature;
private static int _garbageCollectionCounter;
internal void AddOccupiedLocation(Rect buttonFrame, int signature) {
OccupiedLocations.Add((buttonFrame, signature));
}
/// <summary>
/// This method returns a list of all occupied locations. It might be useful but you likely won't have to use it.
/// </summary>
/// <returns>The list containing all of the Rects and their associated IDs.
/// -1 as the signature means that Unity added the Rect.</returns>
public IEnumerable<(Rect buttonFrame, int signature)> GetOccupiedLocations() {
return OccupiedLocations.ToList();
}
/// <summary>
/// Use this method instead of GUI.Button. It will automatically avoid overlap with other buttons.
/// </summary>
/// <param name="buttonFrame">Your preferred location.</param>
/// <param name="text">The text that should be in the button.</param>
/// <param name="signature">The signature from the last time where you generated a button. Pass along 0 if you don't have it.</param>
/// <returns>The methods returns a Tuple containing the modified version of your Rect, the ID that has been assigned to your location,
/// and a bool stating whether or not the generated button has been pressed by the user.</returns>
public (Rect modifiedFrame, int id, bool buttonClicked) CreateButton(Rect buttonFrame, string text, int signature) {
Rect FailureModifier(Rect rect, Rect collidedRect) {
rect.y += collidedRect.height;
return rect;
}
return CreateButton(buttonFrame, text, signature, FailureModifier);
}
/// <summary>
/// Use this method instead of GUI.Button. It will automatically avoid overlap with other buttons.
/// </summary>
/// <param name="buttonFrame">Your preferred location.</param>
/// <param name="text">The text that should be in the button.</param>
/// <param name="signature">The signature from the last time where you generated a button. Pass along 0 if you don't have it.</param>
/// <param name="failureModifier">This method get executed every time where your suggested location isn't valid due to overlap.
/// It receives your Rect, and has to return a modified Rect.</param>
/// <returns>The methods returns a Tuple containing the modified version of your Rect, the ID that has been assigned to your location,
/// and a bool stating whether or not the generated button has been pressed by the user.</returns>
public (Rect modifiedFrame, int id, bool buttonClicked) CreateButton(Rect buttonFrame, string text, int signature, Func<Rect, Rect> failureModifier) {
++_garbageCollectionCounter;
if (ShouldAddRectToOccupiedLocations(ref buttonFrame, signature, failureModifier)) {
OccupiedLocations.Add((buttonFrame, signature == 0 ? ++_signature : signature));
}
_ignoredRects.Add(buttonFrame);
bool buttonClicked = GUI.Button(new Rect(buttonFrame), text);
_ignoredRects.Remove(buttonFrame);
if (_garbageCollectionCounter % 6000 == 0) {
_garbageCollectionCounter = 0;
OccupiedLocations.RemoveAll(occupiedLocation => !occupiedLocation.buttonFrame.Overlaps(new Rect(0, 0, Screen.width, Screen.height)));
GC.Collect();
}
return (buttonFrame, signature == 0 ? _signature : signature, buttonClicked);
}
/// <summary>
/// Use this method instead of GUI.Button. It will automatically avoid overlap with other buttons.
/// </summary>
/// <param name="buttonFrame">Your preferred location.</param>
/// <param name="text">The text that should be in the button.</param>
/// <param name="signature">The signature from the last time where you generated a button. Pass along 0 if you don't have it.</param>
/// <param name="failureModifier">This method get executed every time where your suggested location isn't valid due to overlap.
/// It receives your Rect, the Rect that your Rect collided with, and has to return your modified Rect.</param>
/// <returns>The methods returns a Tuple containing the modified version of your Rect, the ID that has been assigned to your location,
/// and a bool stating whether or not the generated button has been pressed by the user.</returns>
public (Rect modifiedFrame, int id, bool buttonClicked) CreateButton(Rect buttonFrame, string text, int signature, Func<Rect, Rect, Rect> failureModifier) {
++_garbageCollectionCounter;
if (ShouldAddRectToOccupiedLocations(ref buttonFrame, signature, failureModifier)) {
OccupiedLocations.Add((buttonFrame, ++_signature));
}
_ignoredRects.Add(buttonFrame);
bool buttonClicked = GUI.Button(new Rect(buttonFrame), text);
_ignoredRects.Remove(buttonFrame);
if (_garbageCollectionCounter % 6000 == 0) {
_garbageCollectionCounter = 0;
OccupiedLocations.RemoveAll(occupiedLocation => !occupiedLocation.buttonFrame.Overlaps(new Rect(0, 0, Screen.width, Screen.height)));
GC.Collect();
}
return (buttonFrame, signature == 0 ? _signature : signature, buttonClicked);
}
private static bool ShouldAddRectToOccupiedLocations(ref Rect buttonFrame, int signature, Func<Rect, Rect> failureModifier) {
Rect buttonFrameValue;
bool validLocationFound;
bool shouldAddRect = true;
do {
buttonFrameValue = new Rect(buttonFrame);
validLocationFound = true;
foreach ((Rect buttonFrame, int signature) signedLocation in OccupiedLocations.Where(signedLocation => buttonFrameValue.Overlaps(signedLocation.buttonFrame))) {
if (signedLocation.signature != signature) {
buttonFrame = failureModifier(buttonFrame);
validLocationFound = false;
} else {
shouldAddRect = false;
}
}
} while (!validLocationFound);
return shouldAddRect;
}
private static bool ShouldAddRectToOccupiedLocations(ref Rect buttonFrame, int signature, Func<Rect, Rect, Rect> failureModifier) {
Rect buttonFrameValue;
bool validLocationFound;
bool shouldAddRect = true;
do {
buttonFrameValue = new Rect(buttonFrame);
validLocationFound = true;
foreach ((Rect buttonFrame, int signature) signedLocation in OccupiedLocations.Where(signedLocation => buttonFrameValue.Overlaps(signedLocation.buttonFrame))) {
if (signedLocation.signature != signature) {
buttonFrame = failureModifier(buttonFrame, signedLocation.buttonFrame);
validLocationFound = false;
} else {
shouldAddRect = false;
}
}
} while (!validLocationFound);
return shouldAddRect;
}
}
}