@quenty/snackbar 11.3.0 → 11.4.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 +16 -0
- package/default.project.json +1 -0
- package/package.json +26 -6
- package/src/Client/Gui/Snackbar.lua +367 -0
- package/src/Client/SnackbarScreenGuiProvider.lua +11 -0
- package/src/Client/SnackbarServiceClient.lua +46 -42
- package/src/Client/SnackbarServiceClient.story.lua +98 -0
- package/src/Server/SnackbarService.lua +17 -0
- package/src/Shared/SnackbarOptionUtils.lua +25 -0
- package/test/default.project.json +22 -0
- package/test/scripts/Client/ClientMain.client.lua +31 -0
- package/test/scripts/Server/ServerMain.server.lua +12 -0
- package/src/Client/DraggableSnackbar.lua +0 -250
- package/src/Client/Snackbar.lua +0 -292
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [11.4.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/snackbar@11.3.0...@quenty/snackbar@11.4.0) (2024-05-09)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* Fix .package-lock.json replicating in packages ([75d0efe](https://github.com/Quenty/NevermoreEngine/commit/75d0efeef239f221d93352af71a5b3e930ec23c5))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* Refactor snackbar package to new snackbar system ([6f5113b](https://github.com/Quenty/NevermoreEngine/commit/6f5113b3ff1b637f40abb17160ab61788245a05d))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
# [11.3.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/snackbar@11.2.0...@quenty/snackbar@11.3.0) (2024-04-28)
|
|
7
23
|
|
|
8
24
|
|
package/default.project.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/snackbar",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.4.0",
|
|
4
4
|
"description": "Snackbars provide lightweight feedback on an operation at the base of the screen. They automatically disappear after a timeout or user interaction. There can only be one on the screen at a time.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -25,13 +25,33 @@
|
|
|
25
25
|
"Quenty"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@quenty/
|
|
29
|
-
"@quenty/
|
|
30
|
-
"@quenty/
|
|
31
|
-
"@quenty/
|
|
28
|
+
"@quenty/baseobject": "^10.3.0",
|
|
29
|
+
"@quenty/basicpane": "^13.3.0",
|
|
30
|
+
"@quenty/blend": "^12.3.0",
|
|
31
|
+
"@quenty/buttondragmodel": "^1.1.0",
|
|
32
|
+
"@quenty/buttonhighlightmodel": "file:../ButtonHighlightModel",
|
|
33
|
+
"@quenty/ducktype": "^5.3.0",
|
|
34
|
+
"@quenty/genericscreenguiprovider": "^13.5.0",
|
|
35
|
+
"@quenty/inputobjectutils": "^4.4.0",
|
|
36
|
+
"@quenty/lipsum": "^14.3.0",
|
|
37
|
+
"@quenty/loader": "^10.3.0",
|
|
38
|
+
"@quenty/maid": "^3.2.0",
|
|
39
|
+
"@quenty/math": "^2.7.0",
|
|
40
|
+
"@quenty/promise": "^10.3.0",
|
|
41
|
+
"@quenty/promisemaid": "^5.3.0",
|
|
42
|
+
"@quenty/promptqueue": "^1.1.0",
|
|
43
|
+
"@quenty/qgui": "^2.3.0",
|
|
44
|
+
"@quenty/rx": "^13.3.0",
|
|
45
|
+
"@quenty/servicebag": "^11.4.0",
|
|
46
|
+
"@quenty/table": "^3.5.0",
|
|
47
|
+
"@quenty/textserviceutils": "^13.3.0",
|
|
48
|
+
"@quenty/transitionmodel": "^7.3.0",
|
|
49
|
+
"@quenty/utf8": "^2.2.0",
|
|
50
|
+
"@quenty/valueobject": "^13.3.0",
|
|
51
|
+
"@quentystudios/t": "^3.0.1"
|
|
32
52
|
},
|
|
33
53
|
"publishConfig": {
|
|
34
54
|
"access": "public"
|
|
35
55
|
},
|
|
36
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "3fd5cdca3128bf34c8d9dfae1e92d62533b6e6f5"
|
|
37
57
|
}
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
Snackbars provide lightweight feedback on an operation
|
|
3
|
+
at the base of the screen. They automatically disappear
|
|
4
|
+
after a timeout or user interaction. There can only be
|
|
5
|
+
one on the screen at a time.
|
|
6
|
+
|
|
7
|
+
@class Snackbar
|
|
8
|
+
]=]
|
|
9
|
+
|
|
10
|
+
local require = require(script.Parent.loader).load(script)
|
|
11
|
+
|
|
12
|
+
local Blend = require("Blend")
|
|
13
|
+
local ButtonDragModel = require("ButtonDragModel")
|
|
14
|
+
local ButtonHighlightModel = require("ButtonHighlightModel")
|
|
15
|
+
local DuckTypeUtils = require("DuckTypeUtils")
|
|
16
|
+
local Math = require("Math")
|
|
17
|
+
local PromiseMaidUtils = require("PromiseMaidUtils")
|
|
18
|
+
local PromiseUtils = require("PromiseUtils")
|
|
19
|
+
local SnackbarOptionUtils = require("SnackbarOptionUtils")
|
|
20
|
+
local SpringTransitionModel = require("SpringTransitionModel")
|
|
21
|
+
local TransitionModel = require("TransitionModel")
|
|
22
|
+
local UTF8 = require("UTF8")
|
|
23
|
+
local ValueObject = require("ValueObject")
|
|
24
|
+
local SpringObject = require("SpringObject")
|
|
25
|
+
local Table = require("Table")
|
|
26
|
+
|
|
27
|
+
local SHADOW_RADIUS = 2
|
|
28
|
+
local CORNER_RADIUS = 2
|
|
29
|
+
local PADDING_FROM_BOTTOM = 10
|
|
30
|
+
|
|
31
|
+
local DRAG_DISTANCE_TO_HIDE = 48
|
|
32
|
+
local DURATION = 3
|
|
33
|
+
local SHOW_POSITION = UDim2.new(1, 0, 1, 0)
|
|
34
|
+
local HIDE_POSITION = UDim2.new(1, 0, 1, 48)
|
|
35
|
+
|
|
36
|
+
local PADDING_X = 24
|
|
37
|
+
local DEFAULT_TEXT_COLOR = Color3.fromRGB(78, 205, 196)
|
|
38
|
+
|
|
39
|
+
local SnackbarDragDirections = Table.readonly({
|
|
40
|
+
HORIZONTAL = "horizontal";
|
|
41
|
+
VERTICAL = "vertical";
|
|
42
|
+
NONE = "none";
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
local Snackbar = setmetatable({}, TransitionModel)
|
|
46
|
+
Snackbar.ClassName = "Snackbar"
|
|
47
|
+
Snackbar.__index = Snackbar
|
|
48
|
+
|
|
49
|
+
function Snackbar.new(text, options)
|
|
50
|
+
assert(SnackbarOptionUtils.isSnackbarOptions(options) or options == nil, "Bad options")
|
|
51
|
+
options = options or SnackbarOptionUtils.createSnackbarOptions({})
|
|
52
|
+
|
|
53
|
+
local self = setmetatable(TransitionModel.new(), Snackbar)
|
|
54
|
+
|
|
55
|
+
self._text = self._maid:Add(ValueObject.new(text, "string"))
|
|
56
|
+
self._backgroundColor = self._maid:Add(ValueObject.new(Color3.new(0.196, 0.196, 0.196), "Color3"))
|
|
57
|
+
|
|
58
|
+
self._percentVisibleModel = self._maid:Add(SpringTransitionModel.new())
|
|
59
|
+
self._percentVisibleModel:SetEpsilon(1e-2)
|
|
60
|
+
self._percentVisibleModel:SetSpeed(50)
|
|
61
|
+
self._percentVisibleModel:BindToPaneVisbility(self)
|
|
62
|
+
|
|
63
|
+
self._dragSpring = self._maid:Add(SpringObject.new(Vector2.zero, 30))
|
|
64
|
+
|
|
65
|
+
self._positionSpringModel = self._maid:Add(SpringTransitionModel.new(SHOW_POSITION, HIDE_POSITION))
|
|
66
|
+
self._positionSpringModel:SetEpsilon(1e-2)
|
|
67
|
+
self._positionSpringModel:BindToPaneVisbility(self)
|
|
68
|
+
|
|
69
|
+
self._dragModel = self._maid:Add(ButtonDragModel.new())
|
|
70
|
+
self._dragModel:SetClampWithinButton(false)
|
|
71
|
+
|
|
72
|
+
self:SetPromiseShow(function()
|
|
73
|
+
return self._percentVisibleModel:PromiseShow()
|
|
74
|
+
end)
|
|
75
|
+
self:SetPromiseHide(function()
|
|
76
|
+
return self._percentVisibleModel:PromiseHide()
|
|
77
|
+
end)
|
|
78
|
+
|
|
79
|
+
self._maid:GiveTask(self:_render():Subscribe(function(gui)
|
|
80
|
+
self.Gui = gui
|
|
81
|
+
end))
|
|
82
|
+
|
|
83
|
+
if options and options.CallToAction then
|
|
84
|
+
self._maid:GiveTask(self:_renderCallToAction(options.CallToAction):Subscribe(function(button)
|
|
85
|
+
button.Parent = self._callToActionContainer
|
|
86
|
+
end))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
self:_setupDragging()
|
|
90
|
+
|
|
91
|
+
return self
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
function Snackbar.isSnackbar(value)
|
|
95
|
+
return DuckTypeUtils.isImplementation(Snackbar, value)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
function Snackbar:PromiseSustain()
|
|
99
|
+
local promise = PromiseUtils.delayed(DURATION)
|
|
100
|
+
|
|
101
|
+
PromiseMaidUtils.whilePromise(promise, function(maid)
|
|
102
|
+
maid:GiveTask(self.VisibleChanged:Connect(function(isVisible)
|
|
103
|
+
if not isVisible then
|
|
104
|
+
promise:Resolve()
|
|
105
|
+
end
|
|
106
|
+
end))
|
|
107
|
+
end)
|
|
108
|
+
|
|
109
|
+
return promise
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
function Snackbar:_setupDragging()
|
|
113
|
+
self._maid:GiveTask(self._dragSpring:ObserveTarget():Subscribe(function()
|
|
114
|
+
self:_updateHideFromDragTarget()
|
|
115
|
+
end))
|
|
116
|
+
self._maid:GiveTask(self.Gui:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
|
|
117
|
+
self:_updateHideFromDragTarget()
|
|
118
|
+
end))
|
|
119
|
+
|
|
120
|
+
self._maid:GiveTask(self._dragModel:ObserveIsDraggingBrio():Subscribe(function(brio)
|
|
121
|
+
if brio:IsDead() then
|
|
122
|
+
return
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
local maid = brio:ToMaidAndValue()
|
|
126
|
+
local dragDirection = SnackbarDragDirections.NONE
|
|
127
|
+
|
|
128
|
+
maid:GiveTask(self._dragModel:ObserveDragDelta():Subscribe(function(delta)
|
|
129
|
+
if not delta then
|
|
130
|
+
dragDirection = SnackbarDragDirections.NONE
|
|
131
|
+
return
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
if delta.magnitude == 0 then
|
|
135
|
+
return
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
if dragDirection == SnackbarDragDirections.VERTICAL then
|
|
139
|
+
self._dragSpring:SetTarget(Vector2.new(0, delta.y), true)
|
|
140
|
+
elseif dragDirection == SnackbarDragDirections.HORIZONTAL then
|
|
141
|
+
self._dragSpring:SetTarget(Vector2.new(delta.x, 0), true)
|
|
142
|
+
else
|
|
143
|
+
if math.abs(delta.x) > math.abs(delta.y) then
|
|
144
|
+
dragDirection = SnackbarDragDirections.HORIZONTAL
|
|
145
|
+
self._dragSpring:SetTarget(Vector2.new(delta.x, 0), true)
|
|
146
|
+
else
|
|
147
|
+
dragDirection = SnackbarDragDirections.VERTICAL
|
|
148
|
+
self._dragSpring:SetTarget(Vector2.new(0, delta.y), true)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end))
|
|
152
|
+
|
|
153
|
+
maid:GiveTask(function()
|
|
154
|
+
if self._dragSpring.Target.magnitude > 0.5*DRAG_DISTANCE_TO_HIDE then
|
|
155
|
+
self:Hide()
|
|
156
|
+
else
|
|
157
|
+
self._dragSpring:SetTarget(Vector2.zero)
|
|
158
|
+
end
|
|
159
|
+
end)
|
|
160
|
+
end))
|
|
161
|
+
|
|
162
|
+
self:_updateHideFromDragTarget()
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
function Snackbar:_updateHideFromDragTarget(doNotAnimate)
|
|
166
|
+
local target = self._dragSpring.Target
|
|
167
|
+
local guiSize = self.Gui.AbsoluteSize
|
|
168
|
+
local hideTarget
|
|
169
|
+
if target.y > 0 then
|
|
170
|
+
-- Down
|
|
171
|
+
hideTarget = SHOW_POSITION + UDim2.new(0, 0, 0, guiSize.Y)
|
|
172
|
+
elseif target.y < 0 then
|
|
173
|
+
-- Up
|
|
174
|
+
hideTarget = SHOW_POSITION + UDim2.new(0, 0, 0, -guiSize.Y)
|
|
175
|
+
elseif target.x < 0 then
|
|
176
|
+
hideTarget = SHOW_POSITION + UDim2.new(0, -guiSize.X, 0, 0)
|
|
177
|
+
elseif target.x > 0 then
|
|
178
|
+
hideTarget = SHOW_POSITION + UDim2.new(0, guiSize.X, 0, 0)
|
|
179
|
+
else
|
|
180
|
+
hideTarget = SHOW_POSITION + UDim2.new(0, 0, 0, guiSize.Y)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
self._positionSpringModel:SetHideTarget(hideTarget, doNotAnimate)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
function Snackbar:_render()
|
|
188
|
+
local percentDragHidden = Blend.Computed(self._dragSpring:Observe(), function(value)
|
|
189
|
+
return math.clamp(Math.map(value.magnitude, 0, DRAG_DISTANCE_TO_HIDE, 0, 1), 0, 1)
|
|
190
|
+
end)
|
|
191
|
+
self._computedTransparency = Blend.Computed(self._percentVisibleModel:Observe(), percentDragHidden, function(visible, dragHidden)
|
|
192
|
+
return Math.map(visible, 0, 1, 1, dragHidden)
|
|
193
|
+
end)
|
|
194
|
+
|
|
195
|
+
return Blend.New "Frame" {
|
|
196
|
+
ZIndex = 1;
|
|
197
|
+
Name = "Snackbar";
|
|
198
|
+
Size = UDim2.new(0, 0, 0, 0);
|
|
199
|
+
AutomaticSize = Enum.AutomaticSize.XY;
|
|
200
|
+
Position = Blend.Computed(self._positionSpringModel:Observe(), self._dragSpring:Observe(), function(position, dragPosition)
|
|
201
|
+
return position + UDim2.fromOffset(dragPosition.x, dragPosition.y)
|
|
202
|
+
end);
|
|
203
|
+
AnchorPoint = Vector2.new(1, 1);
|
|
204
|
+
BackgroundTransparency = 1;
|
|
205
|
+
|
|
206
|
+
Blend.New "UIPadding" {
|
|
207
|
+
PaddingTop = UDim.new(0, PADDING_FROM_BOTTOM);
|
|
208
|
+
PaddingBottom = UDim.new(0, PADDING_FROM_BOTTOM);
|
|
209
|
+
PaddingLeft = UDim.new(0, PADDING_FROM_BOTTOM);
|
|
210
|
+
PaddingRight = UDim.new(0, PADDING_FROM_BOTTOM);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
Blend.New "UIListLayout" {
|
|
214
|
+
FillDirection = Enum.FillDirection.Horizontal;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
Blend.New "ImageButton" {
|
|
218
|
+
Size = UDim2.new(0, 0, 0, 0);
|
|
219
|
+
AnchorPoint = Vector2.new(1, 1);
|
|
220
|
+
AutomaticSize = Enum.AutomaticSize.XY;
|
|
221
|
+
BackgroundTransparency = 1;
|
|
222
|
+
AutoButtonColor = false;
|
|
223
|
+
|
|
224
|
+
[Blend.Instance] = function(gui)
|
|
225
|
+
self._mainButton = gui
|
|
226
|
+
self._dragModel:SetButton(gui)
|
|
227
|
+
end;
|
|
228
|
+
|
|
229
|
+
Blend.New "UICorner" {
|
|
230
|
+
CornerRadius = UDim.new(0, CORNER_RADIUS);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
Blend.New "UIListLayout" {
|
|
234
|
+
FillDirection = Enum.FillDirection.Horizontal;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
Blend.New "Folder" {
|
|
239
|
+
Name = "BackingFolder";
|
|
240
|
+
|
|
241
|
+
Blend.New "ImageLabel" {
|
|
242
|
+
Name = "Shadow";
|
|
243
|
+
ZIndex = 1;
|
|
244
|
+
Image = "rbxassetid://191838004";
|
|
245
|
+
AnchorPoint = Vector2.new(0.5, 0.5);
|
|
246
|
+
Position = UDim2.fromScale(0.5, 0.5);
|
|
247
|
+
ImageRectSize = Vector2.new(150, 150);
|
|
248
|
+
ScaleType = Enum.ScaleType.Slice;
|
|
249
|
+
SliceCenter = Rect.new(50, 50, 100, 100);
|
|
250
|
+
SliceScale = (2*SHADOW_RADIUS + CORNER_RADIUS)/50;
|
|
251
|
+
BackgroundTransparency = 1;
|
|
252
|
+
Size = UDim2.new(1, 2*SHADOW_RADIUS, 1, 2*SHADOW_RADIUS);
|
|
253
|
+
ImageTransparency = Blend.Computed(self._computedTransparency, function(value)
|
|
254
|
+
return Math.map(value, 0, 1, 0.75, 1)
|
|
255
|
+
end);
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
Blend.New "Frame" {
|
|
260
|
+
Name = "Container";
|
|
261
|
+
AutomaticSize = Enum.AutomaticSize.XY;
|
|
262
|
+
Size = UDim2.new(0, 0, 0, 0);
|
|
263
|
+
ZIndex = 2;
|
|
264
|
+
BackgroundColor3 = self._backgroundColor:Observe();
|
|
265
|
+
BackgroundTransparency = self._computedTransparency;
|
|
266
|
+
|
|
267
|
+
Blend.New "UISizeConstraint" {
|
|
268
|
+
MaxSize = Vector2.new(700, 48*2);
|
|
269
|
+
MinSize = Vector2.new(100, 0);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
[Blend.Instance] = function(gui)
|
|
273
|
+
self._callToActionContainer = gui
|
|
274
|
+
end;
|
|
275
|
+
|
|
276
|
+
Blend.New "UICorner" {
|
|
277
|
+
CornerRadius = UDim.new(0, CORNER_RADIUS);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
Blend.New "UIListLayout" {
|
|
281
|
+
FillDirection = Enum.FillDirection.Horizontal;
|
|
282
|
+
HorizontalAlignment = Enum.HorizontalAlignment.Left;
|
|
283
|
+
VerticalAlignment = Enum.VerticalAlignment.Center;
|
|
284
|
+
Padding = UDim.new(0, PADDING_X);
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
Blend.New "UIPadding" {
|
|
288
|
+
PaddingTop = UDim.new(0, 15);
|
|
289
|
+
PaddingBottom = UDim.new(0, 15);
|
|
290
|
+
PaddingLeft = UDim.new(0, PADDING_X);
|
|
291
|
+
PaddingRight = UDim.new(0, PADDING_X);
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
Blend.New "TextLabel" {
|
|
296
|
+
Name = "SnackbarLabel";
|
|
297
|
+
LayoutOrder = 1;
|
|
298
|
+
BackgroundTransparency = 1;
|
|
299
|
+
AutomaticSize = Enum.AutomaticSize.XY;
|
|
300
|
+
TextColor3 = Color3.new(1, 1, 1);
|
|
301
|
+
Size = UDim2.new(0, 0, 0, 0);
|
|
302
|
+
Position = UDim2.new(0, 0, 0, 18);
|
|
303
|
+
TextXAlignment = Enum.TextXAlignment.Left;
|
|
304
|
+
TextYAlignment = Enum.TextYAlignment.Center;
|
|
305
|
+
BorderSizePixel = 0;
|
|
306
|
+
Font = Enum.Font.SourceSans;
|
|
307
|
+
Text = self._text:Observe();
|
|
308
|
+
TextWrapped = false;
|
|
309
|
+
TextTransparency = Blend.Computed(self._computedTransparency, function(value)
|
|
310
|
+
return Math.map(value, 0, 1, 0.13, 1)
|
|
311
|
+
end);
|
|
312
|
+
TextSize = 18;
|
|
313
|
+
|
|
314
|
+
[Blend.Instance] = function(gui)
|
|
315
|
+
self._textLabel = gui
|
|
316
|
+
end;
|
|
317
|
+
};
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
function Snackbar:_renderCallToAction(callToAction)
|
|
324
|
+
local callToActionText = ""
|
|
325
|
+
local onClick = nil
|
|
326
|
+
if type(callToAction) == "string" then
|
|
327
|
+
callToActionText = callToAction
|
|
328
|
+
else
|
|
329
|
+
callToActionText = tostring(callToAction.Text)
|
|
330
|
+
onClick = callToAction.OnClick
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
local buttonModel = self._maid:Add(ButtonHighlightModel.new())
|
|
334
|
+
|
|
335
|
+
return Blend.New "TextButton" {
|
|
336
|
+
Name = "CallToActionButton";
|
|
337
|
+
AnchorPoint = Vector2.new(1, 0.5);
|
|
338
|
+
LayoutOrder = 2;
|
|
339
|
+
BackgroundTransparency = 1;
|
|
340
|
+
Position = UDim2.new(1, 0, 0.5, 0);
|
|
341
|
+
AutomaticSize = Enum.AutomaticSize.X;
|
|
342
|
+
Size = UDim2.new(0, 0, 0, 18);
|
|
343
|
+
Text = UTF8.upper(callToActionText);
|
|
344
|
+
Font = Enum.Font.SourceSans;
|
|
345
|
+
FontSize = self._textLabel.FontSize;
|
|
346
|
+
TextXAlignment = Enum.TextXAlignment.Right;
|
|
347
|
+
TextColor3 = Blend.Computed(buttonModel:ObservePercentHighlighted(), function(percent)
|
|
348
|
+
local scale = Math.map(percent, 0, 1, 0, 0.2)
|
|
349
|
+
return DEFAULT_TEXT_COLOR:Lerp(Color3.new(0, 0, 0), scale)
|
|
350
|
+
end);
|
|
351
|
+
TextTransparency = self._computedTransparency;
|
|
352
|
+
|
|
353
|
+
[Blend.Instance] = function(gui)
|
|
354
|
+
buttonModel:SetButton(gui)
|
|
355
|
+
end;
|
|
356
|
+
|
|
357
|
+
[Blend.OnEvent "Activated"] = function()
|
|
358
|
+
self:Hide()
|
|
359
|
+
|
|
360
|
+
if onClick then
|
|
361
|
+
onClick()
|
|
362
|
+
end
|
|
363
|
+
end;
|
|
364
|
+
}
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
return Snackbar
|
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
local require = require(script.Parent.loader).load(script)
|
|
7
7
|
|
|
8
|
-
local
|
|
8
|
+
local Snackbar = require("Snackbar")
|
|
9
|
+
local SnackbarScreenGuiProvider = require("SnackbarScreenGuiProvider")
|
|
10
|
+
local Maid = require("Maid")
|
|
11
|
+
local SnackbarOptionUtils = require("SnackbarOptionUtils")
|
|
12
|
+
local PromptQueue = require("PromptQueue")
|
|
9
13
|
|
|
10
14
|
local SnackbarServiceClient = {}
|
|
11
15
|
SnackbarServiceClient.ServiceName = "SnackbarServiceClient"
|
|
@@ -13,8 +17,12 @@ SnackbarServiceClient.ServiceName = "SnackbarServiceClient"
|
|
|
13
17
|
function SnackbarServiceClient:Init(serviceBag)
|
|
14
18
|
assert(not self._serviceBag, "Already initialized")
|
|
15
19
|
self._serviceBag = assert(serviceBag, "No serviceBag")
|
|
20
|
+
self._maid = Maid.new()
|
|
16
21
|
|
|
17
|
-
self.
|
|
22
|
+
self._snackbarScreenGuiProvider = self._serviceBag:GetService(SnackbarScreenGuiProvider)
|
|
23
|
+
self._screenGui = self._maid:Add(self._snackbarScreenGuiProvider:Get("SNACKBAR"))
|
|
24
|
+
|
|
25
|
+
self._queue = self._maid:Add(PromptQueue.new())
|
|
18
26
|
end
|
|
19
27
|
|
|
20
28
|
--[=[
|
|
@@ -29,54 +37,50 @@ function SnackbarServiceClient:SetScreenGui(screenGui)
|
|
|
29
37
|
return self
|
|
30
38
|
end
|
|
31
39
|
|
|
32
|
-
--
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
--[=[
|
|
41
|
+
Makes a snackbar and shows it to the user
|
|
42
|
+
|
|
43
|
+
If options are included, in this format, a call to action will be presented to the player
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
{
|
|
47
|
+
CallToAction = {
|
|
48
|
+
Text = "Action";
|
|
49
|
+
OnClick = function() end;
|
|
42
50
|
};
|
|
43
|
-
|
|
51
|
+
};
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
@param text string
|
|
55
|
+
@param options SnackbarOptions
|
|
56
|
+
]=]
|
|
44
57
|
function SnackbarServiceClient:ShowSnackbar(text, options)
|
|
45
58
|
assert(type(text) == "string", "text must be a string")
|
|
59
|
+
assert(SnackbarOptionUtils.isSnackbarOptions(options) or options == nil, "Bad snackbarOptions")
|
|
46
60
|
|
|
47
|
-
local snackbar =
|
|
48
|
-
self
|
|
61
|
+
local snackbar = Snackbar.new(text, options)
|
|
62
|
+
snackbar.Gui.Parent = self._screenGui
|
|
63
|
+
|
|
64
|
+
self._queue:HideCurrent()
|
|
65
|
+
|
|
66
|
+
self._maid:GivePromise(self._queue:Queue(snackbar))
|
|
67
|
+
:Finally(function()
|
|
68
|
+
snackbar:Destroy()
|
|
69
|
+
end)
|
|
49
70
|
|
|
50
71
|
return snackbar
|
|
51
72
|
end
|
|
52
73
|
|
|
53
|
-
function SnackbarServiceClient:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
self._currentSnackbar:Dismiss()
|
|
64
|
-
self._currentSnackbar = nil
|
|
65
|
-
dismissedSnackbar = true
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
self._currentSnackbar = snackbar
|
|
70
|
-
if dismissedSnackbar then
|
|
71
|
-
task.delay(snackbar.FadeTime, function()
|
|
72
|
-
if self._currentSnackbar == snackbar then
|
|
73
|
-
snackbar:Show()
|
|
74
|
-
end
|
|
75
|
-
end)
|
|
76
|
-
else
|
|
77
|
-
snackbar:Show()
|
|
78
|
-
end
|
|
79
|
-
end
|
|
74
|
+
function SnackbarServiceClient:HideCurrent(doNotAnimate)
|
|
75
|
+
return self._queue:HideCurrent(doNotAnimate)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
function SnackbarServiceClient:ClearQueue(doNotAnimate)
|
|
79
|
+
self._queue:Clear(doNotAnimate)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
function SnackbarServiceClient:Destroy()
|
|
83
|
+
self._maid:DoCleaning()
|
|
80
84
|
end
|
|
81
85
|
|
|
82
86
|
return SnackbarServiceClient
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
@class snackbarServiceClient.story
|
|
3
|
+
]]
|
|
4
|
+
|
|
5
|
+
local require = require(game:GetService("ServerScriptService"):FindFirstChild("LoaderUtils", true).Parent).bootstrapStory(script)
|
|
6
|
+
|
|
7
|
+
local Maid = require("Maid")
|
|
8
|
+
local ServiceBag = require("ServiceBag")
|
|
9
|
+
local SnackbarServiceClient = require("SnackbarServiceClient")
|
|
10
|
+
local ScreenGuiService = require("ScreenGuiService")
|
|
11
|
+
local LipsumUtils = require("LipsumUtils")
|
|
12
|
+
local Blend = require("Blend")
|
|
13
|
+
|
|
14
|
+
return function(target)
|
|
15
|
+
local maid = Maid.new()
|
|
16
|
+
local serviceBag = maid:Add(ServiceBag.new())
|
|
17
|
+
|
|
18
|
+
local snackbarServiceClient = serviceBag:GetService(SnackbarServiceClient)
|
|
19
|
+
maid:GiveTask(serviceBag:GetService(ScreenGuiService):SetGuiParent(target))
|
|
20
|
+
|
|
21
|
+
serviceBag:Init()
|
|
22
|
+
serviceBag:Start()
|
|
23
|
+
|
|
24
|
+
local function showSnackBar()
|
|
25
|
+
snackbarServiceClient:ShowSnackbar(LipsumUtils.sentence(10), {
|
|
26
|
+
CallToAction = {
|
|
27
|
+
Text = LipsumUtils.word();
|
|
28
|
+
OnClick = function()
|
|
29
|
+
print("Activated action")
|
|
30
|
+
end;
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
local function button(props)
|
|
36
|
+
return Blend.New "TextButton" {
|
|
37
|
+
AutomaticSize = Enum.AutomaticSize.XY;
|
|
38
|
+
AutoButtonColor = true;
|
|
39
|
+
Text = props.Text;
|
|
40
|
+
[Blend.OnEvent "Activated"] = props.OnActivated;
|
|
41
|
+
|
|
42
|
+
Blend.New "UIPadding" {
|
|
43
|
+
PaddingTop = UDim.new(0, 10);
|
|
44
|
+
PaddingBottom = UDim.new(0, 10);
|
|
45
|
+
PaddingLeft = UDim.new(0, 10);
|
|
46
|
+
PaddingRight = UDim.new(0, 10);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
Blend.New "UICorner" {
|
|
50
|
+
CornerRadius = UDim.new(0, 5);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
showSnackBar()
|
|
56
|
+
|
|
57
|
+
maid:GiveTask(Blend.mount(target, {
|
|
58
|
+
Blend.New "Frame" {
|
|
59
|
+
AutomaticSize = Enum.AutomaticSize.XY;
|
|
60
|
+
AnchorPoint = Vector2.new(0.5, 0);
|
|
61
|
+
Position = UDim2.fromScale(0.5, 0);
|
|
62
|
+
BackgroundTransparency = 1;
|
|
63
|
+
|
|
64
|
+
Blend.New "UIPadding" {
|
|
65
|
+
PaddingTop = UDim.new(0, 5);
|
|
66
|
+
PaddingBottom = UDim.new(0, 5);
|
|
67
|
+
PaddingLeft = UDim.new(0, 5);
|
|
68
|
+
PaddingRight = UDim.new(0, 5);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
Blend.New "UIListLayout" {
|
|
72
|
+
FillDirection = Enum.FillDirection.Horizontal;
|
|
73
|
+
Padding = UDim.new(0, 5);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
button({
|
|
77
|
+
Text = "Show snackbar";
|
|
78
|
+
OnActivated = showSnackBar;
|
|
79
|
+
});
|
|
80
|
+
button({
|
|
81
|
+
Text = "Clear queue";
|
|
82
|
+
OnActivated = function()
|
|
83
|
+
snackbarServiceClient:ClearQueue()
|
|
84
|
+
end;
|
|
85
|
+
});
|
|
86
|
+
button({
|
|
87
|
+
Text = "Hide current";
|
|
88
|
+
OnActivated = function()
|
|
89
|
+
snackbarServiceClient:HideCurrent()
|
|
90
|
+
end;
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
}))
|
|
94
|
+
|
|
95
|
+
return function()
|
|
96
|
+
maid:DoCleaning()
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
@class SnackbarService
|
|
3
|
+
]=]
|
|
4
|
+
|
|
5
|
+
local require = require(script.Parent.loader).load(script)
|
|
6
|
+
|
|
7
|
+
local SnackbarService = {}
|
|
8
|
+
SnackbarService.ServiceName = "SnackbarService"
|
|
9
|
+
|
|
10
|
+
function SnackbarService:Init(serviceBag)
|
|
11
|
+
assert(not self._serviceBag, "Already initialized")
|
|
12
|
+
self._serviceBag = assert(serviceBag, "No serviceBag")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
return SnackbarService
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
@class SnackbarOptionUtils
|
|
3
|
+
|
|
4
|
+
]=]
|
|
5
|
+
|
|
6
|
+
local require = require(script.Parent.loader).load(script)
|
|
7
|
+
|
|
8
|
+
local t = require("t")
|
|
9
|
+
|
|
10
|
+
local SnackbarOptionUtils = {}
|
|
11
|
+
|
|
12
|
+
function SnackbarOptionUtils.createSnackbarOptions(options)
|
|
13
|
+
assert(SnackbarOptionUtils.isSnackbarOptions(options))
|
|
14
|
+
|
|
15
|
+
return options
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
SnackbarOptionUtils.isSnackbarOptions = t.interface({
|
|
19
|
+
CallToAction = t.optional(t.union(t.string, t.interface({
|
|
20
|
+
Text = t.string;
|
|
21
|
+
OnClick = t.optional(t.callback);
|
|
22
|
+
})));
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return SnackbarOptionUtils
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "SnackbarTest",
|
|
3
|
+
"globIgnorePaths": [ "**/.package-lock.json" ],
|
|
4
|
+
"tree": {
|
|
5
|
+
"$className": "DataModel",
|
|
6
|
+
"ServerScriptService": {
|
|
7
|
+
"snackbar": {
|
|
8
|
+
"$path": ".."
|
|
9
|
+
},
|
|
10
|
+
"Script": {
|
|
11
|
+
"$path": "scripts/Server"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"StarterPlayer": {
|
|
15
|
+
"StarterPlayerScripts": {
|
|
16
|
+
"Main": {
|
|
17
|
+
"$path": "scripts/Client"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
@class ClientMain
|
|
3
|
+
]]
|
|
4
|
+
local loader = game:GetService("ReplicatedStorage"):WaitForChild("snackbar"):WaitForChild("loader")
|
|
5
|
+
local require = require(loader).bootstrapGame(loader.Parent)
|
|
6
|
+
|
|
7
|
+
local serviceBag = require("ServiceBag").new()
|
|
8
|
+
serviceBag:GetService(require("SnackbarServiceClient"))
|
|
9
|
+
serviceBag:Init()
|
|
10
|
+
serviceBag:Start()
|
|
11
|
+
|
|
12
|
+
local snackbarServiceClient = serviceBag:GetService(require("SnackbarServiceClient"))
|
|
13
|
+
|
|
14
|
+
local LipsumUtils = require("LipsumUtils")
|
|
15
|
+
|
|
16
|
+
local function showSnackbar()
|
|
17
|
+
snackbarServiceClient:ShowSnackbar(LipsumUtils.sentence(5), {
|
|
18
|
+
CallToAction = {
|
|
19
|
+
Text = LipsumUtils.word();
|
|
20
|
+
OnClick = function()
|
|
21
|
+
print("Activated action")
|
|
22
|
+
end;
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
showSnackbar()
|
|
28
|
+
|
|
29
|
+
workspace:WaitForChild("Part").ProximityPrompt.Triggered:Connect(function()
|
|
30
|
+
showSnackbar()
|
|
31
|
+
end)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
@class ServerMain
|
|
3
|
+
]]
|
|
4
|
+
local ServerScriptService = game:GetService("ServerScriptService")
|
|
5
|
+
|
|
6
|
+
local loader = ServerScriptService:FindFirstChild("LoaderUtils", true).Parent
|
|
7
|
+
local require = require(loader).bootstrapGame(ServerScriptService.snackbar)
|
|
8
|
+
|
|
9
|
+
local serviceBag = require("ServiceBag").new()
|
|
10
|
+
serviceBag:GetService(require("SnackbarService"))
|
|
11
|
+
serviceBag:Init()
|
|
12
|
+
serviceBag:Start()
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
--[=[
|
|
2
|
-
Snackbar, but draggable
|
|
3
|
-
@class DraggableSnackbar
|
|
4
|
-
]=]
|
|
5
|
-
|
|
6
|
-
local require = require(script.Parent.loader).load(script)
|
|
7
|
-
|
|
8
|
-
local UserInputService = game:GetService("UserInputService")
|
|
9
|
-
local RunService = game:GetService("RunService")
|
|
10
|
-
local Players = game:GetService("Players")
|
|
11
|
-
local GuiService = game:GetService("GuiService")
|
|
12
|
-
|
|
13
|
-
local Snackbar = require("Snackbar")
|
|
14
|
-
local qGUI = require("qGUI")
|
|
15
|
-
|
|
16
|
-
local DraggableSnackbar = setmetatable({}, Snackbar)
|
|
17
|
-
DraggableSnackbar.ClassName = "DraggableSnackbar"
|
|
18
|
-
DraggableSnackbar.__index = DraggableSnackbar
|
|
19
|
-
DraggableSnackbar.Vertical = true
|
|
20
|
-
DraggableSnackbar.DefaultFadeOut = "FadeOutDown"
|
|
21
|
-
DraggableSnackbar.Duration = 3
|
|
22
|
-
|
|
23
|
-
-- By default the Snackbar will close automatically if the user types outside or presses the esc key.
|
|
24
|
-
DraggableSnackbar.AutoCloseDisabled = false
|
|
25
|
-
|
|
26
|
-
-- Note that this will not show until :Show() is called
|
|
27
|
-
-- @constructor
|
|
28
|
-
-- @param [GCOnDismissal] If true, will destroy itself and GC after being dismissed. Defaults to true
|
|
29
|
-
-- @param [Options] Table of optional values, adds call to actions, et cetera
|
|
30
|
-
function DraggableSnackbar.new(Parent, Text, GCOnDismissal, Options)
|
|
31
|
-
local self = setmetatable(Snackbar.new(Parent, Text, Options), DraggableSnackbar)
|
|
32
|
-
|
|
33
|
-
self._visible = false
|
|
34
|
-
self._draggingCoroutine = nil
|
|
35
|
-
self._shouldDismiss = false
|
|
36
|
-
self._showId = 0
|
|
37
|
-
|
|
38
|
-
self._mouse = Players.LocalPlayer:GetMouse()
|
|
39
|
-
self._gcOnDismissal = GCOnDismissal == nil and true or false
|
|
40
|
-
|
|
41
|
-
-- Set to transparency and faded out direction automatically
|
|
42
|
-
self[self.DefaultFadeOut](self, true)
|
|
43
|
-
|
|
44
|
-
return self
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
function DraggableSnackbar:Show()
|
|
48
|
-
if not self._visible then
|
|
49
|
-
self._visible = true
|
|
50
|
-
self:FadeIn()
|
|
51
|
-
local LocalShowId = self._showId + 1
|
|
52
|
-
self._showId = LocalShowId
|
|
53
|
-
|
|
54
|
-
-- Connect events
|
|
55
|
-
self._whileActiveMaid:GiveTask(self.Gui.MouseButton1Down:Connect(function(X, Y)
|
|
56
|
-
if self._showId == LocalShowId then
|
|
57
|
-
if not self._draggingCoroutine then
|
|
58
|
-
self:StartTrack(X, Y)
|
|
59
|
-
end
|
|
60
|
-
else
|
|
61
|
-
warn("[DraggingBeginEvent] - self._showId ~= LocalShowId, but event fired")
|
|
62
|
-
end
|
|
63
|
-
end))
|
|
64
|
-
|
|
65
|
-
self._whileActiveMaid:GiveTask(UserInputService.InputBegan:Connect(function(InputObject, GameProcessedEvent)
|
|
66
|
-
if GameProcessedEvent then
|
|
67
|
-
return
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
if self._showId ~= LocalShowId then
|
|
71
|
-
warn("[InputDismissEvent] - self._showId ~= LocalShowId, but event fired")
|
|
72
|
-
return
|
|
73
|
-
end
|
|
74
|
-
if self.AutoCloseDisabled then
|
|
75
|
-
return
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
if qGUI.MouseOver(self._mouse, self.Gui) then
|
|
79
|
-
return
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
if self.AbsolutePosition ~= self.Gui.AbsolutePosition then
|
|
83
|
-
return -- Animating / dragging
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
if InputObject.UserInputType == Enum.UserInputType.Touch
|
|
87
|
-
or InputObject.UserInputType == Enum.UserInputType.MouseButton1 then
|
|
88
|
-
|
|
89
|
-
self:Dismiss()
|
|
90
|
-
end
|
|
91
|
-
end))
|
|
92
|
-
|
|
93
|
-
-- Setup hide on dismissal
|
|
94
|
-
task.delay(self.Duration, function()
|
|
95
|
-
if self.Destroy and self._showId == LocalShowId and self._visible then
|
|
96
|
-
self:Dismiss()
|
|
97
|
-
end
|
|
98
|
-
end)
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
function DraggableSnackbar:StartTrack(X, Y)
|
|
103
|
-
if self.Vertical then
|
|
104
|
-
self._startDragPosition = Y
|
|
105
|
-
else
|
|
106
|
-
self._startDragPosition = X
|
|
107
|
-
end
|
|
108
|
-
self.DragOffset = 0
|
|
109
|
-
|
|
110
|
-
local localDraggingCoroutine
|
|
111
|
-
localDraggingCoroutine = coroutine.create(function()
|
|
112
|
-
while self._draggingCoroutine == localDraggingCoroutine do
|
|
113
|
-
self:Track()
|
|
114
|
-
RunService.Stepped:Wait()
|
|
115
|
-
end
|
|
116
|
-
end)
|
|
117
|
-
self._draggingCoroutine = localDraggingCoroutine
|
|
118
|
-
|
|
119
|
-
self._whileActiveMaid.DraggingEnded = UserInputService.InputEnded:Connect(function(inputObject)
|
|
120
|
-
if self._draggingCoroutine == localDraggingCoroutine then
|
|
121
|
-
if inputObject.UserInputType.Name == "MouseButton1" then
|
|
122
|
-
self:EndTrack()
|
|
123
|
-
end
|
|
124
|
-
else
|
|
125
|
-
warn("[DraggableSnackbar] - InputEnded fire, but DraggingCoroutine was not the localDraggingCoroutine")
|
|
126
|
-
end
|
|
127
|
-
end)
|
|
128
|
-
|
|
129
|
-
self._whileActiveMaid.TouchDraggingEnded = UserInputService.TouchEnded:Connect(function(_)
|
|
130
|
-
if self._draggingCoroutine == localDraggingCoroutine then
|
|
131
|
-
self:EndTrack()
|
|
132
|
-
else
|
|
133
|
-
warn("[DraggableSnackbar] - TouchEnded fire, but DraggingCoroutine was not the localDraggingCoroutine")
|
|
134
|
-
end
|
|
135
|
-
end)
|
|
136
|
-
|
|
137
|
-
assert(coroutine.resume(self._draggingCoroutine))
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
function DraggableSnackbar:Track()
|
|
141
|
-
local DragOffset, DragLength
|
|
142
|
-
local TopLeftInset, _ = GuiService:GetGuiInset()
|
|
143
|
-
|
|
144
|
-
if self.Vertical then
|
|
145
|
-
DragOffset = (self._mouse.Y + TopLeftInset.Y) - self._startDragPosition
|
|
146
|
-
DragLength = self.Gui.AbsoluteSize.Y
|
|
147
|
-
|
|
148
|
-
self.Gui.Position = self.Position + UDim2.new(0, 0, 0, DragOffset)
|
|
149
|
-
else
|
|
150
|
-
DragOffset = (self._mouse.X + TopLeftInset.X) - self._startDragPosition
|
|
151
|
-
DragLength = self.Gui.AbsoluteSize.Y
|
|
152
|
-
|
|
153
|
-
self.Gui.Position = self.Position + UDim2.new(0, DragOffset, 0, 0)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
local PercentFaded = math.abs(DragOffset) / DragLength
|
|
157
|
-
if PercentFaded > 1 then
|
|
158
|
-
PercentFaded = 1
|
|
159
|
-
elseif PercentFaded < 0 then
|
|
160
|
-
PercentFaded = 0
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
self:FadeOutTransparency(PercentFaded)
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
function DraggableSnackbar:GetOffsetXY()
|
|
167
|
-
local Offset = self.Gui.AbsolutePosition - self.AbsolutePosition
|
|
168
|
-
return Offset
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
function DraggableSnackbar:EndTrack()
|
|
172
|
-
self._startDragPosition = nil
|
|
173
|
-
self._draggingCoroutine = nil
|
|
174
|
-
|
|
175
|
-
-- Cleanup events
|
|
176
|
-
self._whileActiveMaid.DraggingEnded = nil
|
|
177
|
-
self._whileActiveMaid.TouchDraggingEnded = nil
|
|
178
|
-
|
|
179
|
-
-- Dismissal if dragged out
|
|
180
|
-
if self._shouldDismiss then
|
|
181
|
-
self:Dismiss()
|
|
182
|
-
else
|
|
183
|
-
local OffsetXY = self:GetOffsetXY()
|
|
184
|
-
local SizeXY = self.Gui.AbsoluteSize
|
|
185
|
-
|
|
186
|
-
if math.abs(OffsetXY.X) >= SizeXY.X or math.abs(OffsetXY.Y) >= SizeXY.Y then
|
|
187
|
-
self:Dismiss()
|
|
188
|
-
else
|
|
189
|
-
self:FadeIn()
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
function DraggableSnackbar:Dismiss()
|
|
195
|
-
if self._visible then
|
|
196
|
-
if self._draggingCoroutine then
|
|
197
|
-
self._shouldDismiss = true
|
|
198
|
-
else
|
|
199
|
-
self._visible = false
|
|
200
|
-
self._shouldDismiss = nil
|
|
201
|
-
self._whileActiveMaid:DoCleaning()
|
|
202
|
-
|
|
203
|
-
local OffsetXY = self:GetOffsetXY()
|
|
204
|
-
-- Determine what direction to fade out...
|
|
205
|
-
if OffsetXY.X > 0 then
|
|
206
|
-
self:FadeOutRight()
|
|
207
|
-
elseif OffsetXY.X < 0 then
|
|
208
|
-
self:FadeOutLeft()
|
|
209
|
-
elseif OffsetXY.Y > 0 then
|
|
210
|
-
self:FadeOutUp()
|
|
211
|
-
elseif OffsetXY.Y < 0 then
|
|
212
|
-
self:FadeOutDown()
|
|
213
|
-
else
|
|
214
|
-
self[self.DefaultFadeOut](self)
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
-- GC stuff
|
|
218
|
-
if self._gcOnDismissal then
|
|
219
|
-
self._gcOnDismissal = false -- Make sure this is only called once...
|
|
220
|
-
delay(self.FadeTime, function()
|
|
221
|
-
self:Destroy()
|
|
222
|
-
end)
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
else
|
|
226
|
-
warn("[DraggableSnackbar] - Cannot dismiss, already hidden")
|
|
227
|
-
end
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
function DraggableSnackbar:IsVisible()
|
|
231
|
-
return self._visible
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
function DraggableSnackbar:Destroy()
|
|
235
|
-
self.Gui:Destroy()
|
|
236
|
-
self.Gui = nil
|
|
237
|
-
|
|
238
|
-
self.TextLabel:Destroy()
|
|
239
|
-
self.TextLabel = nil
|
|
240
|
-
|
|
241
|
-
self._whileActiveMaid:DoCleaning()
|
|
242
|
-
self._whileActiveMaid = nil
|
|
243
|
-
|
|
244
|
-
self._visible = false
|
|
245
|
-
self._draggingCoroutine = nil
|
|
246
|
-
|
|
247
|
-
setmetatable(self, nil)
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
return DraggableSnackbar
|
package/src/Client/Snackbar.lua
DELETED
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
--[=[
|
|
2
|
-
Snackbars provide lightweight feedback on an operation
|
|
3
|
-
at the base of the screen. They automatically disappear
|
|
4
|
-
after a timeout or user interaction. There can only be
|
|
5
|
-
one on the screen at a time.
|
|
6
|
-
@class Snackbar
|
|
7
|
-
]=]
|
|
8
|
-
|
|
9
|
-
local require = require(script.Parent.loader).load(script)
|
|
10
|
-
|
|
11
|
-
local qGUI = require("qGUI")
|
|
12
|
-
local Maid = require("Maid")
|
|
13
|
-
local Math = require("Math")
|
|
14
|
-
|
|
15
|
-
-- Base clase, not functional
|
|
16
|
-
local Snackbar = {}
|
|
17
|
-
Snackbar.ClassName = "Snackbar"
|
|
18
|
-
Snackbar.__index = Snackbar
|
|
19
|
-
Snackbar.Height = 48
|
|
20
|
-
Snackbar.MinimumWidth = 288 -- Taken from google material design
|
|
21
|
-
Snackbar.MaximumWidth = 700
|
|
22
|
-
Snackbar.TextWidthOffset = 24
|
|
23
|
-
Snackbar.Position = UDim2.new(1, -10, 1, -10 - Snackbar.Height)
|
|
24
|
-
Snackbar.FadeTime = 0.16
|
|
25
|
-
Snackbar.CornerRadius = 2--24
|
|
26
|
-
|
|
27
|
-
local DEFAULT_TEXT_COLOR = Color3.fromRGB(78, 205, 196)
|
|
28
|
-
|
|
29
|
-
function Snackbar.new(Parent, Text, options)
|
|
30
|
-
local self = setmetatable({}, Snackbar)
|
|
31
|
-
|
|
32
|
-
local Gui = Instance.new("ImageButton")
|
|
33
|
-
Gui.ZIndex = 7
|
|
34
|
-
Gui.Name = "Snackbar"
|
|
35
|
-
Gui.Size = UDim2.new(0, 100, 0, self.Height)
|
|
36
|
-
Gui.BorderSizePixel = 0
|
|
37
|
-
Gui.BackgroundColor3 = Color3.new(0.196, 0.196, 0.196) -- Google design specifications
|
|
38
|
-
Gui.Archivable = false
|
|
39
|
-
Gui.ClipsDescendants = false
|
|
40
|
-
Gui.Position = self.Position
|
|
41
|
-
Gui.AutoButtonColor = false
|
|
42
|
-
Gui.BackgroundTransparency = 1
|
|
43
|
-
self.Gui = Gui
|
|
44
|
-
|
|
45
|
-
self.BackgroundImages = {qGUI.BackWithRoundedRectangle(Gui, self.CornerRadius, Gui.BackgroundColor3)}
|
|
46
|
-
|
|
47
|
-
local ShadowRadius = 1
|
|
48
|
-
local ShadowContainer = Instance.new("Frame")
|
|
49
|
-
ShadowContainer.AnchorPoint = Vector2.new(0.5, 0.5)
|
|
50
|
-
ShadowContainer.Parent = Gui
|
|
51
|
-
ShadowContainer.Name = "ShadowContainer"
|
|
52
|
-
ShadowContainer.BackgroundTransparency = 1
|
|
53
|
-
ShadowContainer.Size = UDim2.new(1, ShadowRadius*2, 1, ShadowRadius*2)
|
|
54
|
-
ShadowContainer.Archivable = false
|
|
55
|
-
ShadowContainer.Position = UDim2.new(0.5, 0, 0.5, 0)
|
|
56
|
-
|
|
57
|
-
-- Image is blurred at
|
|
58
|
-
self.ShadowImages = {
|
|
59
|
-
qGUI.AddNinePatch(ShadowContainer, "rbxassetid://191838004",
|
|
60
|
-
Vector2.new(150, 150),
|
|
61
|
-
self.CornerRadius + ShadowRadius,
|
|
62
|
-
"ImageLabel"
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
for _, item in pairs(self.ShadowImages) do
|
|
67
|
-
item.ImageTransparency = 0.74
|
|
68
|
-
item.ZIndex = Gui.ZIndex - 2
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
for _, item in pairs(self.BackgroundImages) do
|
|
72
|
-
item.ZIndex = Gui.ZIndex - 1
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
local textLabel = Instance.new("TextLabel")
|
|
76
|
-
textLabel.Size = UDim2.new(1, -self.TextWidthOffset*2, 0, 16)
|
|
77
|
-
textLabel.Position = UDim2.new(0, self.TextWidthOffset, 0, 16)
|
|
78
|
-
textLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
79
|
-
textLabel.TextYAlignment = Enum.TextYAlignment.Center
|
|
80
|
-
textLabel.Name = "SnackbarLabel"
|
|
81
|
-
textLabel.TextTransparency = 0.87
|
|
82
|
-
textLabel.TextColor3 = Color3.new(1, 1, 1)
|
|
83
|
-
textLabel.BackgroundTransparency = 1
|
|
84
|
-
textLabel.BorderSizePixel = 0
|
|
85
|
-
textLabel.Font = Enum.Font.SourceSans
|
|
86
|
-
textLabel.Text = Text
|
|
87
|
-
textLabel.FontSize = Enum.FontSize.Size18
|
|
88
|
-
textLabel.ZIndex = Gui.ZIndex-1
|
|
89
|
-
textLabel.Parent = Gui
|
|
90
|
-
self._textLabel = textLabel
|
|
91
|
-
|
|
92
|
-
self._whileActiveMaid = Maid.new()
|
|
93
|
-
self.Gui.Parent = Parent
|
|
94
|
-
|
|
95
|
-
local callToActionText
|
|
96
|
-
if options and options.CallToAction then
|
|
97
|
-
if type(options.CallToAction) == "string" then
|
|
98
|
-
callToActionText = options.CallToAction
|
|
99
|
-
else
|
|
100
|
-
callToActionText = tostring(options.CallToAction.Text)
|
|
101
|
-
end
|
|
102
|
-
callToActionText = callToActionText:upper()
|
|
103
|
-
|
|
104
|
-
local button = Instance.new("TextButton")
|
|
105
|
-
button.Name = "CallToActionButton"
|
|
106
|
-
button.AnchorPoint = Vector2.new(1, 0.5)
|
|
107
|
-
button.BackgroundTransparency = 1
|
|
108
|
-
button.Position = UDim2.new(1, -self.TextWidthOffset, 0.5, 0)
|
|
109
|
-
button.Size = UDim2.new(0.5, 0, 0.8, 0)
|
|
110
|
-
button.Text = callToActionText
|
|
111
|
-
button.Font = Enum.Font.SourceSans
|
|
112
|
-
button.FontSize = textLabel.FontSize
|
|
113
|
-
button.TextXAlignment = Enum.TextXAlignment.Right
|
|
114
|
-
button.TextColor3 = DEFAULT_TEXT_COLOR
|
|
115
|
-
button.ZIndex = Gui.ZIndex
|
|
116
|
-
button.Parent = Gui
|
|
117
|
-
|
|
118
|
-
-- Resize
|
|
119
|
-
button.Size = UDim2.new(UDim.new(0, button.TextBounds.X), button.Size.Y)
|
|
120
|
-
|
|
121
|
-
self._whileActiveMaid:GiveTask(button.MouseButton1Click:Connect(function()
|
|
122
|
-
if options.CallToAction.OnClick then
|
|
123
|
-
self:Dismiss()
|
|
124
|
-
options.CallToAction.OnClick()
|
|
125
|
-
end
|
|
126
|
-
end))
|
|
127
|
-
|
|
128
|
-
self._whileActiveMaid:GiveTask(button.MouseEnter:Connect(function()
|
|
129
|
-
button.TextColor3 = DEFAULT_TEXT_COLOR:lerp(Color3.new(0, 0, 0), 0.2)
|
|
130
|
-
end))
|
|
131
|
-
|
|
132
|
-
self._whileActiveMaid:GiveTask(button.MouseLeave:Connect(function()
|
|
133
|
-
button.TextColor3 = DEFAULT_TEXT_COLOR
|
|
134
|
-
end))
|
|
135
|
-
|
|
136
|
-
self._callToActionButton = button
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
local width = self._textLabel.TextBounds.X + self.TextWidthOffset*2
|
|
141
|
-
if self._callToActionButton then
|
|
142
|
-
width = width + self._callToActionButton.Size.X.Offset + self.TextWidthOffset*2
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
if width < self.MinimumWidth then
|
|
146
|
-
width = self.MinimumWidth
|
|
147
|
-
elseif width > self.MaximumWidth then
|
|
148
|
-
width = self.MaximumWidth
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
if callToActionText then
|
|
152
|
-
self._textLabel.Text = Text
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
self.Gui.Size = UDim2.new(0, width, 0, self.Height)
|
|
156
|
-
|
|
157
|
-
self.Position = self.Position + UDim2.new(0, -width, 0, 0)
|
|
158
|
-
self.Gui.Position = self.Position
|
|
159
|
-
self.AbsolutePosition = self.Gui.AbsolutePosition
|
|
160
|
-
|
|
161
|
-
return self
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
function Snackbar:Dismiss()
|
|
165
|
-
error("Not implemented")
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
function Snackbar:SetBackgroundTransparency(Transparency)
|
|
169
|
-
for _, item in pairs(self.BackgroundImages) do
|
|
170
|
-
item.ImageTransparency = Transparency
|
|
171
|
-
end
|
|
172
|
-
for _, item in pairs(self.ShadowImages) do
|
|
173
|
-
item.ImageTransparency = Math.map(Transparency, 0, 1, 0.74, 1)
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
function Snackbar:FadeOutTransparency(PercentFaded)
|
|
178
|
-
if PercentFaded then
|
|
179
|
-
self:SetBackgroundTransparency(Math.map(PercentFaded, 0, 1, 0, 1))
|
|
180
|
-
self._textLabel.TextTransparency = Math.map(PercentFaded, 0, 1, 0.13, 1)
|
|
181
|
-
|
|
182
|
-
if self._callToActionButton then
|
|
183
|
-
self._callToActionButton.TextTransparency = PercentFaded
|
|
184
|
-
end
|
|
185
|
-
else
|
|
186
|
-
local newProperties = {
|
|
187
|
-
ImageTransparency = 1;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
for _, item in pairs(self.BackgroundImages) do
|
|
191
|
-
qGUI.TweenTransparency(item, newProperties, self.FadeTime, true)
|
|
192
|
-
end
|
|
193
|
-
for _, item in pairs(self.ShadowImages) do
|
|
194
|
-
qGUI.TweenTransparency(item, newProperties, self.FadeTime, true)
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
qGUI.TweenTransparency(self._textLabel, {
|
|
198
|
-
TextTransparency = 1;
|
|
199
|
-
}, self.FadeTime, true)
|
|
200
|
-
|
|
201
|
-
if self._callToActionButton then
|
|
202
|
-
qGUI.TweenTransparency(self._callToActionButton, {
|
|
203
|
-
TextTransparency = 1;
|
|
204
|
-
}, self.FadeTime, true)
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
-- Will animate unless given PercentFaded
|
|
210
|
-
function Snackbar:FadeInTransparency(PercentFaded)
|
|
211
|
-
if PercentFaded then
|
|
212
|
-
-- self.Gui.BackgroundTransparency = Math.map(PercentFaded, 0, 1, 1, 0)
|
|
213
|
-
self:SetBackgroundTransparency(Math.map(PercentFaded, 0, 1, 1, 0))
|
|
214
|
-
self._textLabel.TextTransparency = Math.map(PercentFaded, 0, 1, 1, 0.13)
|
|
215
|
-
|
|
216
|
-
if self._callToActionButton then
|
|
217
|
-
self._callToActionButton.TextTransparency = PercentFaded
|
|
218
|
-
end
|
|
219
|
-
else
|
|
220
|
-
-- Should be an ease-in-out transparency fade.
|
|
221
|
-
do
|
|
222
|
-
local newProperties = {
|
|
223
|
-
ImageTransparency = 0;
|
|
224
|
-
}
|
|
225
|
-
for _, item in pairs(self.BackgroundImages) do
|
|
226
|
-
qGUI.TweenTransparency(item, newProperties, self.FadeTime, true)
|
|
227
|
-
end
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
do
|
|
231
|
-
local newProperties = {
|
|
232
|
-
ImageTransparency = 0.74;
|
|
233
|
-
}
|
|
234
|
-
for _, item in pairs(self.ShadowImages) do
|
|
235
|
-
qGUI.TweenTransparency(item, newProperties, self.FadeTime, true)
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
qGUI.TweenTransparency(self._textLabel, {
|
|
240
|
-
TextTransparency = 0.13;
|
|
241
|
-
}, self.FadeTime, true)
|
|
242
|
-
|
|
243
|
-
if self._callToActionButton then
|
|
244
|
-
qGUI.TweenTransparency(self._callToActionButton, {
|
|
245
|
-
TextTransparency = 0;
|
|
246
|
-
}, self.FadeTime, true)
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
-- Utility function
|
|
252
|
-
function Snackbar:FadeHandler(NewPosition, DoNotAnimate, IsFadingOut)
|
|
253
|
-
assert(NewPosition, "[Snackbar] - Internal function should not have been called. Missing NewPosition")
|
|
254
|
-
|
|
255
|
-
if IsFadingOut then
|
|
256
|
-
self:FadeOutTransparency(DoNotAnimate and 1 or nil)
|
|
257
|
-
else
|
|
258
|
-
self:FadeInTransparency(DoNotAnimate and 1 or nil)
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
if DoNotAnimate then
|
|
262
|
-
self.Gui.Position = NewPosition
|
|
263
|
-
else
|
|
264
|
-
self.Gui:TweenPosition(NewPosition, "InOut", "Quad", self.FadeTime, true)
|
|
265
|
-
end
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
function Snackbar:FadeOutUp(DoNotAnimate)
|
|
269
|
-
local NewPosition = self.Position + UDim2.new(0, 0, 0, -self.Gui.AbsoluteSize.Y)
|
|
270
|
-
self:FadeHandler(NewPosition, DoNotAnimate, true)
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
function Snackbar:FadeOutDown(DoNotAnimate)
|
|
274
|
-
local NewPosition = self.Position + UDim2.new(0, 0, 0, self.Gui.AbsoluteSize.Y)
|
|
275
|
-
self:FadeHandler(NewPosition, DoNotAnimate, true)
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
function Snackbar:FadeOutRight(DoNotAnimate)
|
|
279
|
-
local NewPosition = self.Position + UDim2.new(0, self.Gui.AbsoluteSize.X, 0, 0)
|
|
280
|
-
self:FadeHandler(NewPosition, DoNotAnimate, true)
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
function Snackbar:FadeOutLeft(DoNotAnimate)
|
|
284
|
-
local NewPosition = self.Position + UDim2.new(0, -self.Gui.AbsoluteSize.X, 0, 0)
|
|
285
|
-
self:FadeHandler(NewPosition, DoNotAnimate, true)
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
function Snackbar:FadeIn(DoNotAnimate)
|
|
289
|
-
self:FadeHandler(self.Position, DoNotAnimate, false)
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
return Snackbar
|