@quenty/buttondragmodel 1.0.1-canary.468.ea8a226.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ ## 1.0.1-canary.468.ea8a226.0 (2024-05-09)
7
+
8
+
9
+ ### Features
10
+
11
+ * Add ButtonDragModel ([401a108](https://github.com/Quenty/NevermoreEngine/commit/401a108c422e5582298ac69a6d50a34a771492b4))
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2014-2024 James Onnen (Quenty)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ ## ButtonDragModel
2
+
3
+ <div align="center">
4
+ <a href="http://quenty.github.io/NevermoreEngine/">
5
+ <img src="https://github.com/Quenty/NevermoreEngine/actions/workflows/docs.yml/badge.svg" alt="Documentation status" />
6
+ </a>
7
+ <a href="https://discord.gg/mhtGUS8">
8
+ <img src="https://img.shields.io/discord/385151591524597761?color=5865F2&label=discord&logo=discord&logoColor=white" alt="Discord" />
9
+ </a>
10
+ <a href="https://github.com/Quenty/NevermoreEngine/actions">
11
+ <img src="https://github.com/Quenty/NevermoreEngine/actions/workflows/build.yml/badge.svg" alt="Build and release status" />
12
+ </a>
13
+ </div>
14
+
15
+ Model for dragging buttons around
16
+
17
+ <div align="center"><a href="https://quenty.github.io/NevermoreEngine/api/ButtonDragModelUtils">View docs →</a></div>
18
+
19
+ ## Installation
20
+
21
+ ```
22
+ npm install @quenty/buttondragmodel --save
23
+ ```
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "buttondragmodel",
3
+ "globIgnorePaths": [ "**/.package-lock.json" ],
4
+ "tree": {
5
+ "$path": "src"
6
+ }
7
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@quenty/buttondragmodel",
3
+ "version": "1.0.1-canary.468.ea8a226.0",
4
+ "description": "Model for dragging buttons around",
5
+ "keywords": [
6
+ "Roblox",
7
+ "Nevermore",
8
+ "Lua",
9
+ "buttondragmodel"
10
+ ],
11
+ "bugs": {
12
+ "url": "https://github.com/Quenty/NevermoreEngine/issues"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/Quenty/NevermoreEngine.git",
17
+ "directory": "src/buttondragmodel/"
18
+ },
19
+ "funding": {
20
+ "type": "patreon",
21
+ "url": "https://www.patreon.com/quenty"
22
+ },
23
+ "license": "MIT",
24
+ "contributors": [
25
+ "Quenty"
26
+ ],
27
+ "dependencies": {
28
+ "@quenty/baseobject": "10.2.1-canary.468.ea8a226.0",
29
+ "@quenty/inputobjectutils": "4.3.1-canary.468.ea8a226.0",
30
+ "@quenty/loader": "10.2.1-canary.468.ea8a226.0",
31
+ "@quenty/maid": "3.1.1-canary.468.ea8a226.0",
32
+ "@quenty/rx": "13.2.1-canary.468.ea8a226.0",
33
+ "@quenty/valueobject": "13.2.1-canary.468.ea8a226.0"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "gitHead": "ea8a2260a364aaf6d6c246a93632b789935da134"
39
+ }
@@ -0,0 +1,342 @@
1
+ --[=[
2
+ Computes the position of a user dragging a button around
3
+
4
+ @class ButtonDragModel
5
+ ]=]
6
+
7
+ local require = require(script.Parent.loader).load(script)
8
+
9
+ local UserInputService = game:GetService("UserInputService")
10
+
11
+ local BaseObject = require("BaseObject")
12
+ local Maid = require("Maid")
13
+ local InputObjectUtils = require("InputObjectUtils")
14
+ local ValueObject = require("ValueObject")
15
+
16
+ local ButtonDragModel = setmetatable({}, BaseObject)
17
+ ButtonDragModel.ClassName = "ButtonDragModel"
18
+ ButtonDragModel.__index = ButtonDragModel
19
+
20
+ --[=[
21
+ Construst a new drag model for the button
22
+
23
+ @param initialButton GuiButton? -- Optional
24
+ @return ButtonDragModel
25
+ ]=]
26
+ function ButtonDragModel.new(initialButton)
27
+ local self = setmetatable(BaseObject.new(), ButtonDragModel)
28
+
29
+ self._isMouseDown = self._maid:Add(ValueObject.new(false, "boolean"))
30
+ self._dragPosition = self._maid:Add(ValueObject.new(nil))
31
+ self._dragDelta = self._maid:Add(ValueObject.new(nil))
32
+ self._button = self._maid:Add(ValueObject.new(nil))
33
+
34
+ self._absoluteSize = self._maid:Add(ValueObject.new(Vector2.zero, "Vector2"))
35
+ self._isDragging = self._maid:Add(ValueObject.new(false, "boolean"))
36
+ self._clampWithinButton = self._maid:Add(ValueObject.new(false, "boolean"))
37
+
38
+ self._activePositions = {}
39
+
40
+ self._maid:GiveTask(self._dragPosition.Changed:Connect(function()
41
+ self._isDragging.Value = self._dragPosition.Value ~= nil
42
+ end))
43
+
44
+ self.DragPositionChanged = self._dragPosition.Changed
45
+ self.IsDraggingChanged = self._isDragging.Changed
46
+
47
+ if initialButton then
48
+ self:SetButton(initialButton)
49
+ end
50
+
51
+ self._maid:GiveTask(self._button:ObserveBrio(function(button)
52
+ return button ~= nil
53
+ end):Subscribe(function(brio)
54
+ if brio:IsDead() then
55
+ return
56
+ end
57
+
58
+ local maid, button = brio:ToMaidAndValue()
59
+ self:_setupDragging(maid, button)
60
+ end))
61
+
62
+ return self
63
+ end
64
+
65
+ --[=[
66
+ Observes if anything is pressing down on the button itself
67
+
68
+ @return Observable<boolean>
69
+ ]=]
70
+ function ButtonDragModel:ObserveIsDragging()
71
+ return self._isDragging:Observe()
72
+ end
73
+
74
+ --[=[
75
+ @return Observable<Brio<true>>
76
+ ]=]
77
+ function ButtonDragModel:ObserveIsDraggingBrio()
78
+ return self._isDragging:ObserveBrio(function(value)
79
+ return value
80
+ end)
81
+ end
82
+
83
+ --[=[
84
+ @return Observable<Vector2 | nil>
85
+ ]=]
86
+ function ButtonDragModel:ObserveDragDelta()
87
+ return self._dragDelta:Observe()
88
+ end
89
+
90
+ --[=[
91
+ @return Vector2 | nil
92
+ ]=]
93
+ function ButtonDragModel:GetDragDelta()
94
+ return self._dragDelta.Value
95
+ end
96
+
97
+ --[=[
98
+ Returns true if pressed
99
+
100
+ @return boolean
101
+ ]=]
102
+ function ButtonDragModel:GetIsPressed()
103
+ return self._isDragging.Value
104
+ end
105
+
106
+ --[=[
107
+ Returns the scale position on the Gui from 0 to 1
108
+
109
+ This is reletive to the GUI, so top left is 0, 0
110
+
111
+ @return Vector2 | nil
112
+ ]=]
113
+ function ButtonDragModel:GetDragPosition()
114
+ return self._dragPosition.Value
115
+ end
116
+
117
+ --[=[
118
+ Observes the scale position on the Gui from 0 to 1
119
+
120
+ This is reletive to the GUI, so top left is 0, 0
121
+
122
+ @return Observable<Vector2 | nil>
123
+ ]=]
124
+ function ButtonDragModel:ObserveDragPosition()
125
+ return self._dragPosition:Observe()
126
+ end
127
+
128
+ --[=[
129
+ Sets whether to clamp the results within the button bounds
130
+ @param clampWithinButton boolean
131
+ ]=]
132
+ function ButtonDragModel:SetClampWithinButton(clampWithinButton)
133
+ self._clampWithinButton.Value = clampWithinButton
134
+ end
135
+
136
+ --[=[
137
+ Sets the current button for the model
138
+
139
+ @param button GuiButton
140
+ @return () -> () -- Cleanup function
141
+ ]=]
142
+ function ButtonDragModel:SetButton(button)
143
+ assert(typeof(button) == "Instance" or button == nil, "Bad button")
144
+
145
+ self._button.Value = button
146
+
147
+ return function()
148
+ if self._button.Value == button then
149
+ self._button.Value = nil
150
+ end
151
+ end
152
+ end
153
+
154
+ function ButtonDragModel:_setupDragging(maid, button)
155
+ maid:GiveTask(self._clampWithinButton.Changed:Connect(function()
156
+ self:_updateCurrentPosition()
157
+ end))
158
+
159
+ maid:GiveTask(button.InputBegan:Connect(function(inputObject)
160
+ if inputObject.UserInputType == Enum.UserInputType.MouseButton1 then
161
+ self._activePositions.mouse = self:_toButtonSpace(button, inputObject.Position)
162
+ self._isMouseDown.Value = true
163
+ self:_updateCurrentPosition()
164
+ end
165
+
166
+ if inputObject.UserInputType == Enum.UserInputType.Touch then
167
+ self:_trackTouch(maid, button, inputObject)
168
+ end
169
+ end))
170
+
171
+ maid:GiveTask(button.InputEnded:Connect(function(inputObject)
172
+ if inputObject.UserInputType == Enum.UserInputType.MouseButton1 then
173
+ self._activePositions.mouse = nil
174
+ self._isMouseDown.Value = false
175
+ end
176
+
177
+ if inputObject.UserInputType == Enum.UserInputType.Touch then
178
+ self:_stopTouchTrack(maid, inputObject)
179
+ end
180
+ end))
181
+
182
+ maid:GiveTask(UserInputService.InputEnded:Connect(function(inputObject)
183
+ if inputObject.UserInputType == Enum.UserInputType.MouseButton1 then
184
+ self._activePositions.mouse = nil
185
+ self._isMouseDown.Value = false
186
+ end
187
+ end))
188
+
189
+ maid:GiveTask(function()
190
+ self._activePositions.mouse = nil
191
+ self._isMouseDown.Value = false
192
+ end)
193
+
194
+ maid:GiveTask(self._isMouseDown.Changed:Connect(function()
195
+ if self._isMouseDown.Value then
196
+ maid._mouse = self:_updateMouseTracking(button)
197
+ else
198
+ maid._mouse = nil
199
+ end
200
+ end))
201
+ end
202
+
203
+ function ButtonDragModel:_updateMouseTracking(button)
204
+ local maid = Maid.new()
205
+
206
+ local lastMousePosition = nil
207
+
208
+ local function setMousePosition(inputObject)
209
+ local previous = lastMousePosition
210
+ local current = inputObject.Position
211
+
212
+ lastMousePosition = current
213
+
214
+ if previous then
215
+ local delta = current - previous
216
+ self:_incrementDragDelta(delta)
217
+ end
218
+
219
+ self._activePositions.mouse = self:_toButtonSpace(button, current)
220
+ self:_updateCurrentPosition()
221
+ end
222
+
223
+ maid:GiveTask(UserInputService.InputChanged:Connect(function(inputObject)
224
+ if not InputObjectUtils.isMouseUserInputType(inputObject.UserInputType) then
225
+ return
226
+ end
227
+
228
+ local screenGui = button:FindFirstAncestorWhichIsA("ScreenGui")
229
+ if not screenGui then
230
+ self._activePositions.mouse = nil
231
+ self:_updateCurrentPosition()
232
+ return
233
+ end
234
+
235
+ if not (screenGui:FindFirstAncestorWhichIsA("PlayerGui") or screenGui:FindFirstAncestorWhichIsA("CoreGui")) then
236
+ -- TODO: Handle billboard guis
237
+ self._activePositions.mouse = nil
238
+ self:_updateCurrentPosition()
239
+ return
240
+ end
241
+
242
+ setMousePosition(inputObject)
243
+ end))
244
+
245
+ maid:GiveTask(button.InputChanged:Connect(function(inputObject)
246
+ if InputObjectUtils.isMouseUserInputType(inputObject.UserInputType) then
247
+ setMousePosition(inputObject)
248
+ end
249
+ end))
250
+
251
+ maid:GiveTask(function()
252
+ self._activePositions.mouse = nil
253
+ self:_updateCurrentPosition()
254
+ end)
255
+
256
+ return maid
257
+ end
258
+
259
+ function ButtonDragModel:_trackTouch(buttonMaid, button, inputObject)
260
+ buttonMaid[inputObject] = nil
261
+
262
+ if inputObject.UserInputState == Enum.UserInputState.End then
263
+ return
264
+ end
265
+
266
+ local maid = Maid.new()
267
+
268
+ self._activePositions[inputObject] = self:_toButtonSpace(button, inputObject.Position)
269
+ self:_incrementDragDelta(inputObject.Delta)
270
+
271
+ maid:GiveTask(inputObject:GetPropertyChangedSignal("Delta"):Connect(function()
272
+ self:_incrementDragDelta(inputObject.Delta)
273
+ end))
274
+
275
+ maid:GiveTask(inputObject:GetPropertyChangedSignal("Position"):Connect(function()
276
+ self._activePositions[inputObject] = self:_toButtonSpace(button, inputObject.Position)
277
+ self:_updateCurrentPosition()
278
+ end))
279
+ maid:GiveTask(inputObject:GetPropertyChangedSignal("UserInputState"):Connect(function()
280
+ if inputObject.UserInputState == Enum.UserInputState.End then
281
+ maid[inputObject] = nil
282
+ end
283
+ end))
284
+
285
+ maid:GiveTask(function()
286
+ self._activePositions[inputObject] = nil
287
+ self:_updateCurrentPosition()
288
+ end)
289
+
290
+ self:_updateCurrentPosition()
291
+
292
+ maid[inputObject] = maid
293
+ end
294
+
295
+ function ButtonDragModel:_stopTouchTrack(buttonMaid, inputObject)
296
+ -- Clears the input tracking as we slide off the button
297
+ buttonMaid[inputObject] = nil
298
+ end
299
+
300
+ function ButtonDragModel:_toButtonSpace(button, position)
301
+ local pos = button.AbsolutePosition
302
+ local size = button.AbsoluteSize
303
+
304
+ return (Vector2.new(position.x, position.y) - pos)/size
305
+ end
306
+
307
+ function ButtonDragModel:_updateCurrentPosition()
308
+ local current = Vector2.zero
309
+ local count = 0
310
+ for _, item in pairs(self._activePositions) do
311
+ current = current + item
312
+ count = count + 1
313
+ end
314
+ if count == 0 then
315
+ self._dragPosition.Value = nil
316
+ self._dragDelta.Value = nil
317
+ return
318
+ end
319
+
320
+ current = current/count
321
+ local x = current.x
322
+ local y = current.y
323
+
324
+ if self._clampWithinButton.Value then
325
+ x = math.clamp(x, 0, 1)
326
+ y = math.clamp(x, 0, 1)
327
+ end
328
+
329
+ local position = Vector2.new(x, y)
330
+ self._dragPosition.Value = position
331
+
332
+ if not self._dragDelta.Value then
333
+ self._dragDelta.Value = Vector2.zero
334
+ end
335
+ end
336
+
337
+ function ButtonDragModel:_incrementDragDelta(delta)
338
+ local current = self._dragDelta.Value or Vector2.zero
339
+ self._dragDelta.Value = current + Vector2.new(delta.x, delta.y)
340
+ end
341
+
342
+ return ButtonDragModel
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "node_modules",
3
+ "globIgnorePaths": [ "**/.package-lock.json" ],
4
+ "tree": {
5
+ "$path": { "optional": "../node_modules" }
6
+ }
7
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "ButtonDragModelTest",
3
+ "globIgnorePaths": [ "**/.package-lock.json" ],
4
+ "tree": {
5
+ "$className": "DataModel",
6
+ "ServerScriptService": {
7
+ "buttondragmodel": {
8
+ "$path": ".."
9
+ }
10
+ }
11
+ }
12
+ }