@quenty/blend 12.5.0 → 12.5.1-canary.f162cba.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 CHANGED
@@ -3,6 +3,18 @@
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
+ ## [12.5.1-canary.f162cba.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/blend@12.5.0...@quenty/blend@12.5.1-canary.f162cba.0) (2024-09-22)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * SpringObject initializes properly in Blend ([8729989](https://github.com/Quenty/NevermoreEngine/commit/8729989788b482b3a4f0da223e2f2d49c12ff707))
12
+ * SpringObject wouldn't emit a value initially when observing the default spring ([0198add](https://github.com/Quenty/NevermoreEngine/commit/0198add7bf4d124ee70c43466960c99f06b273af))
13
+
14
+
15
+
16
+
17
+
6
18
  # [12.5.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/blend@12.4.0...@quenty/blend@12.5.0) (2024-09-12)
7
19
 
8
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/blend",
3
- "version": "12.5.0",
3
+ "version": "12.5.1-canary.f162cba.0",
4
4
  "description": "Declarative UI system.",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -27,24 +27,24 @@
27
27
  "access": "public"
28
28
  },
29
29
  "dependencies": {
30
- "@quenty/acceltween": "^2.5.0",
31
- "@quenty/brio": "^14.5.0",
32
- "@quenty/ducktype": "^5.4.0",
33
- "@quenty/instanceutils": "^13.5.0",
34
- "@quenty/loader": "^10.4.0",
35
- "@quenty/maid": "^3.3.0",
36
- "@quenty/promise": "^10.4.0",
37
- "@quenty/rx": "^13.5.0",
38
- "@quenty/signal": "^7.4.0",
39
- "@quenty/spring": "^10.4.0",
40
- "@quenty/steputils": "^3.4.0",
41
- "@quenty/string": "^3.2.0",
42
- "@quenty/valuebaseutils": "^13.5.0",
43
- "@quenty/valueobject": "^13.5.0"
30
+ "@quenty/acceltween": "2.5.0",
31
+ "@quenty/brio": "14.5.0",
32
+ "@quenty/ducktype": "5.4.0",
33
+ "@quenty/instanceutils": "13.5.0",
34
+ "@quenty/loader": "10.4.0",
35
+ "@quenty/maid": "3.3.0",
36
+ "@quenty/promise": "10.4.0",
37
+ "@quenty/rx": "13.5.0",
38
+ "@quenty/signal": "7.4.0",
39
+ "@quenty/spring": "10.4.1-canary.f162cba.0",
40
+ "@quenty/steputils": "3.4.0",
41
+ "@quenty/string": "3.2.0",
42
+ "@quenty/valuebaseutils": "13.5.0",
43
+ "@quenty/valueobject": "13.5.0"
44
44
  },
45
45
  "devDependencies": {
46
- "@quenty/contentproviderutils": "^12.5.0",
47
- "@quenty/playerthumbnailutils": "^10.4.0"
46
+ "@quenty/contentproviderutils": "12.5.0",
47
+ "@quenty/playerthumbnailutils": "10.4.0"
48
48
  },
49
- "gitHead": "fb172906f3ee725269ec1e5f4daf9dca227e729d"
49
+ "gitHead": "f162cba4d1b4a8bba2c99cf22be2a44bdee1b198"
50
50
  }
@@ -25,6 +25,13 @@ SpringObject.__index = SpringObject
25
25
 
26
26
  --[=[
27
27
  Constructs a new SpringObject.
28
+
29
+ The spring object is initially initialized as a spring at 0, with a target of 0. Upon setting
30
+ a target or position, it will be initialized and begin emitting events.
31
+
32
+ If two observables emit different types the spring will retain the speed, damper, and switch to
33
+ an initializes.
34
+
28
35
  @param target T
29
36
  @param speed number | Observable<number> | ValueObject<number> | NumberValue | any
30
37
  @param damper number | Observable<number> | NumberValue | any
@@ -46,6 +53,9 @@ function SpringObject.new(target, speed, damper)
46
53
 
47
54
  if target then
48
55
  self:SetTarget(target)
56
+ else
57
+ -- Ensure we initialize at 0 so we can emit a value immediately
58
+ self:SetTarget(0)
49
59
  end
50
60
 
51
61
  if speed then
@@ -129,15 +139,20 @@ function SpringObject:PromiseFinished(signal)
129
139
  signal = signal or RunService.RenderStepped
130
140
 
131
141
  local maid = Maid.new()
132
- local promise = Promise.new()
133
- maid:GiveTask(promise)
142
+ local promise = maid:Add(Promise.new())
134
143
 
135
144
  -- TODO: Mathematical solution?
136
145
  local startAnimate, stopAnimate = StepUtils.bindToSignal(signal, function()
137
- local animating = SpringUtils.animating(self._currentSpring, self._epsilon)
146
+ local currentSpring = rawget(self, "_currentSpring")
147
+ if not currentSpring then
148
+ return false
149
+ end
150
+
151
+ local animating = SpringUtils.animating(currentSpring, self._epsilon)
138
152
  if not animating then
139
153
  promise:Resolve(true)
140
154
  end
155
+
141
156
  return animating
142
157
  end)
143
158
 
@@ -163,12 +178,18 @@ function SpringObject:ObserveVelocityOnSignal(signal)
163
178
  local maid = Maid.new()
164
179
 
165
180
  local startAnimate, stopAnimate = StepUtils.bindToSignal(signal, function()
166
- local animating = SpringUtils.animating(self._currentSpring, self._epsilon)
181
+ local currentSpring = rawget(self, "_currentSpring")
182
+ if not currentSpring then
183
+ return false
184
+ end
185
+
186
+ local animating = SpringUtils.animating(currentSpring, self._epsilon)
167
187
  if animating then
168
- sub:Fire(SpringUtils.fromLinearIfNeeded(self._currentSpring.Velocity))
188
+ sub:Fire(SpringUtils.fromLinearIfNeeded(currentSpring.Velocity))
169
189
  else
170
- sub:Fire(SpringUtils.fromLinearIfNeeded(0*self._currentSpring.Velocity))
190
+ sub:Fire(SpringUtils.fromLinearIfNeeded(0*currentSpring.Velocity))
171
191
  end
192
+
172
193
  return animating
173
194
  end)
174
195
 
@@ -190,7 +211,12 @@ function SpringObject:ObserveOnSignal(signal)
190
211
  local maid = Maid.new()
191
212
 
192
213
  local startAnimate, stopAnimate = StepUtils.bindToSignal(signal, function()
193
- local animating, position = SpringUtils.animating(self._currentSpring, self._epsilon)
214
+ local currentSpring = rawget(self, "_currentSpring")
215
+ if not currentSpring then
216
+ return false
217
+ end
218
+
219
+ local animating, position = SpringUtils.animating(currentSpring, self._epsilon)
194
220
  sub:Fire(SpringUtils.fromLinearIfNeeded(position))
195
221
  return animating
196
222
  end)
@@ -208,7 +234,12 @@ end
208
234
  @return boolean -- True if animating
209
235
  ]=]
210
236
  function SpringObject:IsAnimating()
211
- return (SpringUtils.animating(self._currentSpring, self._epsilon))
237
+ local currentSpring = rawget(self, "_currentSpring")
238
+ if not currentSpring then
239
+ return false
240
+ end
241
+
242
+ return (SpringUtils.animating(currentSpring, self._epsilon))
212
243
  end
213
244
 
214
245
  --[=[
@@ -219,26 +250,29 @@ end
219
250
  @return ()
220
251
  ]=]
221
252
  function SpringObject:Impulse(velocity)
222
- self._currentSpring:Impulse(SpringUtils.toLinearIfNeeded(velocity))
253
+ local converted = SpringUtils.toLinearIfNeeded(velocity)
254
+ local currentSpring = self:_getSpringForType(velocity)
255
+ currentSpring:Impulse(converted)
223
256
  self.Changed:Fire()
224
257
  end
225
258
 
226
259
  --[=[
227
260
  Sets the actual target. If doNotAnimate is set, then animation will be skipped.
228
261
 
229
- @param value T -- The target to set
262
+ @param target T -- The target to set
230
263
  @param doNotAnimate boolean? -- Whether or not to animate
231
- @return ()
232
264
  ]=]
233
- function SpringObject:SetTarget(value, doNotAnimate)
234
- local observable = Blend.toPropertyObservable(value) or Rx.of(value)
265
+ function SpringObject:SetTarget(target, doNotAnimate)
266
+ assert(target ~= nil, "Bad target")
267
+
268
+ local observable = Blend.toPropertyObservable(target) or Rx.of(target)
235
269
 
236
270
  if doNotAnimate then
237
271
  local isFirst = true
238
272
 
239
273
  self._maid._targetSub = observable:Subscribe(function(unconverted)
240
274
  local converted = SpringUtils.toLinearIfNeeded(unconverted)
241
- assert(converted, "Not a valid converted value")
275
+ assert(converted, "Not a valid converted target")
242
276
 
243
277
  local spring = self:_getSpringForType(converted)
244
278
  spring:SetTarget(converted, isFirst)
@@ -250,12 +284,125 @@ function SpringObject:SetTarget(value, doNotAnimate)
250
284
  self._maid._targetSub = observable:Subscribe(function(unconverted)
251
285
  local converted = SpringUtils.toLinearIfNeeded(unconverted)
252
286
  self:_getSpringForType(converted).Target = converted
253
-
254
287
  self.Changed:Fire()
255
288
  end)
256
289
  end
257
290
  end
258
291
 
292
+ --[=[
293
+ Sets the velocity for the spring
294
+
295
+ @param velocity T
296
+ ]=]
297
+ function SpringObject:SetVelocity(velocity)
298
+ assert(velocity ~= nil, "Bad velocity")
299
+
300
+ local observable = Blend.toPropertyObservable(velocity) or Rx.of(velocity)
301
+
302
+ self._maid._velocitySub = observable:Subscribe(function(unconverted)
303
+ local converted = SpringUtils.toLinearIfNeeded(unconverted)
304
+
305
+ self:_getSpringForType(0*converted).Velocity = converted
306
+ self.Changed:Fire()
307
+ end)
308
+ end
309
+
310
+ --[=[
311
+ Sets the position for the spring
312
+
313
+ @param position T
314
+ ]=]
315
+ function SpringObject:SetPosition(position)
316
+ assert(position ~= nil, "Bad position")
317
+
318
+ local observable = Blend.toPropertyObservable(position) or Rx.of(position)
319
+
320
+ self._maid._positionSub = observable:Subscribe(function(unconverted)
321
+ local converted = SpringUtils.toLinearIfNeeded(unconverted)
322
+ self:_getSpringForType(converted).Value = converted
323
+ self.Changed:Fire()
324
+ end)
325
+ end
326
+
327
+ --[=[
328
+ Sets the damper for the spring
329
+
330
+ @param damper number | Observable<number>
331
+ ]=]
332
+ function SpringObject:SetDamper(damper)
333
+ assert(damper ~= nil, "Bad damper")
334
+
335
+ local observable = assert(Blend.toNumberObservable(damper), "Invalid damper")
336
+
337
+ self._maid._damperSub = observable:Subscribe(function(unconverted)
338
+ assert(type(unconverted) == "number", "Bad damper")
339
+
340
+ local currentSpring = rawget(self, "_currentSpring")
341
+ if currentSpring then
342
+ currentSpring.Damper = unconverted
343
+ else
344
+ self:_getInitInfo().Damper = unconverted
345
+ end
346
+
347
+ self.Changed:Fire()
348
+ end)
349
+ end
350
+
351
+ --[=[
352
+ Sets the damper for the spring
353
+
354
+ @param speed number | Observable<number>
355
+ ]=]
356
+ function SpringObject:SetSpeed(speed)
357
+ assert(speed ~= nil, "Bad speed")
358
+
359
+ local observable = assert(Blend.toNumberObservable(speed), "Invalid speed")
360
+
361
+ self._maid._speedSub = observable:Subscribe(function(unconverted)
362
+ assert(type(unconverted) == "number", "Bad damper")
363
+
364
+ local currentSpring = rawget(self, "_currentSpring")
365
+ if currentSpring then
366
+ currentSpring.Speed = unconverted
367
+ else
368
+ self:_getInitInfo().Speed = unconverted
369
+ end
370
+
371
+ self.Changed:Fire()
372
+ end)
373
+ end
374
+
375
+ --[=[
376
+ Sets the clock function for the spring
377
+
378
+ @param clock () -> (number)
379
+ ]=]
380
+ function SpringObject:SetClock(clock)
381
+ assert(type(clock) == "function", "Bad clock clock")
382
+
383
+ local currentSpring = rawget(self, "_currentSpring")
384
+ if currentSpring then
385
+ currentSpring.Clock = clock
386
+ else
387
+ self:_getInitInfo().Clock = clock
388
+ end
389
+
390
+ self.Changed:Fire()
391
+ end
392
+
393
+ --[=[
394
+ Sets the epsilon for the spring to stop animating
395
+
396
+ @param epsilon number
397
+ ]=]
398
+ function SpringObject:SetEpsilon(epsilon)
399
+ assert(type(epsilon) == "number", "Bad epsilon")
400
+
401
+ rawset(self, "_epsilon", epsilon)
402
+
403
+ self.Changed:Fire()
404
+ end
405
+
259
406
  --[=[
260
407
  Instantly skips the spring forwards by that amount time
261
408
  @param delta number -- Time to skip forwards
@@ -264,27 +411,58 @@ end
264
411
  function SpringObject:TimeSkip(delta)
265
412
  assert(type(delta) == "number", "Bad delta")
266
413
 
267
- self._currentSpring:TimeSkip(delta)
414
+ local currentSpring = rawget(self, "_currentSpring")
415
+ if not currentSpring then
416
+ return
417
+ end
418
+
419
+ currentSpring:TimeSkip(delta)
268
420
  self.Changed:Fire()
269
421
  end
270
422
 
271
423
  function SpringObject:__index(index)
272
- if index == "Value" or index == "Position" or index == "p" then
273
- return SpringUtils.fromLinearIfNeeded(self._currentSpring.Value)
424
+ local currentSpring = rawget(self, "_currentSpring")
425
+
426
+ if SpringObject[index] then
427
+ return SpringObject[index]
428
+ elseif index == "Value" or index == "Position" or index == "p" then
429
+ if currentSpring then
430
+ return SpringUtils.fromLinearIfNeeded(currentSpring.Value)
431
+ else
432
+ return 0
433
+ end
274
434
  elseif index == "Velocity" or index == "v" then
275
- return SpringUtils.fromLinearIfNeeded(self._currentSpring.Velocity)
435
+ if currentSpring then
436
+ return SpringUtils.fromLinearIfNeeded(currentSpring.Velocity)
437
+ else
438
+ return 0
439
+ end
276
440
  elseif index == "Target" or index == "t" then
277
- return SpringUtils.fromLinearIfNeeded(self._currentSpring.Target)
441
+ if currentSpring then
442
+ return SpringUtils.fromLinearIfNeeded(currentSpring.Target)
443
+ else
444
+ return 0
445
+ end
278
446
  elseif index == "Damper" or index == "d" then
279
- return self._currentSpring.Damper
447
+ if currentSpring then
448
+ return currentSpring.Damper
449
+ else
450
+ return self:_getInitInfo().Damper
451
+ end
280
452
  elseif index == "Speed" or index == "s" then
281
- return self._currentSpring.Speed
453
+ if currentSpring then
454
+ return currentSpring.Speed
455
+ else
456
+ return self:_getInitInfo().Speed
457
+ end
282
458
  elseif index == "Clock" then
283
- return self._currentSpring.Clock
459
+ if currentSpring then
460
+ return currentSpring.Clock
461
+ else
462
+ return self:_getInitInfo().Clock
463
+ end
284
464
  elseif index == "Epsilon" then
285
465
  return self._epsilon
286
- elseif SpringObject[index] then
287
- return SpringObject[index]
288
466
  elseif index == "_currentSpring" then
289
467
  local found = rawget(self, "_currentSpring")
290
468
  if found then
@@ -293,7 +471,7 @@ function SpringObject:__index(index)
293
471
 
294
472
  -- Note that sometimes the current spring isn't loaded yet as a type so
295
473
  -- we use a number for this.
296
- return self:_getSpringForType(0)
474
+ error("Internal error: Cannot get _currentSpring, as we aren't initialized yet")
297
475
  else
298
476
  error(string.format("%q is not a member of SpringObject", tostring(index)))
299
477
  end
@@ -301,71 +479,56 @@ end
301
479
 
302
480
  function SpringObject:__newindex(index, value)
303
481
  if index == "Value" or index == "Position" or index == "p" then
304
- local observable = Blend.toPropertyObservable(value) or Rx.of(value)
305
-
306
- self._maid._valueSub = observable:Subscribe(function(unconverted)
307
- local converted = SpringUtils.toLinearIfNeeded(unconverted)
308
- self:_getSpringForType(converted).Value = converted
309
- self.Changed:Fire()
310
- end)
482
+ self:SetPosition(value)
311
483
  elseif index == "Velocity" or index == "v" then
312
- local observable = Blend.toPropertyObservable(value) or Rx.of(value)
313
-
314
- self._maid._velocitySub = observable:Subscribe(function(unconverted)
315
- local converted = SpringUtils.toLinearIfNeeded(unconverted)
316
-
317
- self:_getSpringForType(0*converted).Velocity = converted
318
- self.Changed:Fire()
319
- end)
484
+ self:SetVelocity(value)
320
485
  elseif index == "Target" or index == "t" then
321
486
  self:SetTarget(value)
322
487
  elseif index == "Damper" or index == "d" then
323
- local observable = assert(Blend.toNumberObservable(value), "Invalid damper")
324
-
325
- self._maid._damperSub = observable:Subscribe(function(unconverted)
326
- assert(type(unconverted) == "number", "Bad damper")
327
-
328
- self._currentSpring.Damper = unconverted
329
- self.Changed:Fire()
330
- end)
488
+ self:SetDamper(value)
331
489
  elseif index == "Speed" or index == "s" then
332
- local observable = assert(Blend.toNumberObservable(value), "Invalid speed")
333
- assert(self._currentSpring, "No self._currentSpring")
334
-
335
- self._maid._speedSub = observable:Subscribe(function(unconverted)
336
- assert(type(unconverted) == "number", "Bad damper")
337
-
338
- self._currentSpring.Speed = unconverted
339
- self.Changed:Fire()
340
- end)
341
- elseif index == "Epsilon" then
342
- assert(type(value) == "number", "Bad value")
343
- rawset(self, "_epsilon", value)
490
+ self:SetSpeed(value)
344
491
  elseif index == "Clock" then
345
- assert(type(value) == "function", "Bad clock value")
346
- self._currentSpring.Clock = value
347
- self.Changed:Fire()
492
+ self:SetClock(value)
493
+ elseif index == "Epsilon" then
494
+ self:SetEpsilon(value)
348
495
  elseif index == "_currentSpring" then
349
- rawset(self, "_currentSpring", value)
496
+ error("Cannot set _currentSpring")
350
497
  else
351
498
  error(string.format("%q is not a member of SpringObject", tostring(index)))
352
499
  end
353
500
  end
354
501
 
502
+ --[[
503
+ Callers of this must invoke .Changed after using this method
504
+ ]]
355
505
  function SpringObject:_getSpringForType(converted)
356
- if rawget(self, "_currentSpring") == nil then
506
+ local currentSpring = rawget(self, "_currentSpring")
507
+
508
+ if currentSpring == nil then
509
+
357
510
  -- only happens on init
358
- local created = Spring.new(converted)
359
- rawset(self, "_currentSpring", created)
360
- return created
511
+ local newSpring = Spring.new(converted)
512
+
513
+ local foundInitInfo = rawget(self, "_initInfo")
514
+ if foundInitInfo then
515
+ rawset(self, "_initInfo", nil)
516
+ newSpring.Clock = foundInitInfo.Clock
517
+ newSpring.Speed = foundInitInfo.Speed
518
+ newSpring.Damper = foundInitInfo.Damper
519
+ end
520
+
521
+ rawset(self, "_currentSpring", newSpring)
522
+
523
+ return newSpring
361
524
  else
362
- local currentType = typeof(SpringUtils.fromLinearIfNeeded(self._currentSpring.Value))
525
+ local currentType = typeof(SpringUtils.fromLinearIfNeeded(currentSpring.Value))
363
526
  if currentType == typeof(SpringUtils.fromLinearIfNeeded(converted)) then
364
- return self._currentSpring
527
+ return currentSpring
365
528
  else
366
- local oldDamper = self._currentSpring.d
367
- local oldSpeed = self._currentSpring.s
368
- local clock = self._currentSpring.Clock
529
+ local oldDamper = currentSpring.d
530
+ local oldSpeed = currentSpring.s
531
+ local clock = currentSpring.Clock
369
532
 
370
533
  local newSpring = Spring.new(converted)
371
534
  newSpring.Clock = clock
@@ -377,6 +540,28 @@ function SpringObject:_getSpringForType(converted)
377
540
  end
378
541
  end
379
542
 
543
+ function SpringObject:_getInitInfo()
544
+ local currentSpring = rawget(self, "_currentSpring")
545
+ if currentSpring then
546
+ error("Should not have currentSpring")
547
+ end
548
+
549
+ local foundInitInfo = rawget(self, "_initInfo")
550
+ if foundInitInfo then
551
+ return foundInitInfo
552
+ end
553
+
554
+ local value = {
555
+ Clock = os.clock;
556
+ Damper = 1;
557
+ Speed = 1;
558
+ }
559
+
560
+ rawset(self, "_initInfo", value)
561
+
562
+ return value
563
+ end
564
+
380
565
  --[=[
381
566
  Cleans up the BaseObject and sets the metatable to nil
382
567
  ]=]