@quenty/blend 12.5.0 → 12.5.1

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