react-native-vroom-chart 0.16.0 → 0.18.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.
Files changed (44) hide show
  1. package/README.md +1 -1
  2. package/android/CMakeLists.txt +12 -2
  3. package/android/README.md +16 -6
  4. package/android/build.gradle +10 -0
  5. package/android/src/main/cpp/OnLoad.cpp +10 -0
  6. package/android/src/main/cpp/VroomChartJsiBindings.cpp +35 -0
  7. package/android/src/main/cpp/VroomChartJsiBindings.h +27 -0
  8. package/android/src/main/java/com/vroom/chart/VroomChartModule.kt +15 -11
  9. package/cpp/VroomChartHostObject.cpp +81 -0
  10. package/cpp/VroomJsiInstaller.cpp +6 -0
  11. package/cpp/_core_include/vroom/vroom_chart.h +41 -0
  12. package/cpp/_core_src/candles.cpp +15 -14
  13. package/cpp/_core_src/chart.cpp +297 -28
  14. package/cpp/_core_src/chart.h +71 -2
  15. package/cpp/_core_src/chart_facade.cpp +27 -0
  16. package/cpp/_core_src/color_lerp.h +28 -0
  17. package/cpp/_core_src/loading_line.cpp +183 -0
  18. package/cpp/_core_src/loading_line.h +38 -0
  19. package/cpp/_core_src/loading_wave.h +140 -0
  20. package/cpp/_core_src/ma_overlay.cpp +15 -10
  21. package/cpp/_core_src/ma_overlay.h +7 -0
  22. package/cpp/_core_src/price_indicator.cpp +21 -7
  23. package/cpp/_core_src/price_indicator.h +11 -1
  24. package/cpp/_core_src/price_indicator_anim.h +81 -0
  25. package/cpp/_core_src/theme.cpp +5 -0
  26. package/cpp/_core_src/tip_geometry.h +55 -0
  27. package/cpp/_core_src/viewport.h +33 -0
  28. package/ios/README.md +17 -5
  29. package/ios/VroomChartModule.h +3 -6
  30. package/ios/VroomChartModule.mm +17 -23
  31. package/lib/index.d.mts +30 -0
  32. package/lib/index.d.ts +30 -0
  33. package/lib/index.js +75 -14
  34. package/lib/index.js.map +1 -1
  35. package/lib/index.mjs +75 -14
  36. package/lib/index.mjs.map +1 -1
  37. package/package.json +9 -9
  38. package/react-native-vroom-chart.podspec +1 -1
  39. package/src/NativeVroomChart.ts +7 -4
  40. package/src/VroomChart.tsx +12 -0
  41. package/src/jsi.d.ts +42 -0
  42. package/src/theme.ts +1 -0
  43. package/src/useChartCore.ts +129 -7
  44. package/android/src/main/cpp/VroomChartJni.cpp +0 -24
package/README.md CHANGED
@@ -15,7 +15,7 @@ import { VroomChart } from 'react-native-vroom-chart';
15
15
 
16
16
  ## Peer dependencies
17
17
 
18
- - `react-native` ≥ 0.76
18
+ - `react-native` ≥ 0.78, New Architecture enabled — the chart installs its JSI bindings through the TurboModule JSI-bindings hooks, which have no legacy-bridge equivalent
19
19
  - `@shopify/react-native-skia` ≥ 2.11.0 — provides the Skia runtime we draw into. vroom links RN-Skia's native internals (`cpp/api`) and tracks that package closely; those headers have no semver guarantee.
20
20
 
21
21
  - `react-native-gesture-handler` ≥ 2.16 — long-press / pan / pinch recognition
@@ -2,7 +2,8 @@
2
2
  #
3
3
  # Builds libvroomchart.so from the same platform-agnostic JSI glue and chart
4
4
  # core the iOS podspec compiles (see ../react-native-vroom-chart.podspec),
5
- # plus this directory's small JNI entry point (src/main/cpp/VroomChartJni.cpp).
5
+ # plus this directory's fbjni entry points (src/main/cpp/OnLoad.cpp and
6
+ # src/main/cpp/VroomChartJsiBindings.cpp).
6
7
  #
7
8
  # RN-Skia's headers (JsiSkNativeObjects.h, JsiSkPicture.h, and the
8
9
  # RNSkPlatformContext.h they pull in) aren't consumed via CMake's prefab
@@ -27,6 +28,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
27
28
  set(CMAKE_POSITION_INDEPENDENT_CODE ON)
28
29
 
29
30
  find_package(ReactAndroid REQUIRED CONFIG)
31
+ find_package(fbjni REQUIRED CONFIG)
30
32
  # Prefab package name matches the Gradle project name AGP autolinking assigns
31
33
  # RN-Skia (":shopify_react-native-skia"), not the "rnskia" module name inside
32
34
  # its own `prefab { rnskia { ... } }` block — that block key becomes the
@@ -70,7 +72,8 @@ add_library(vroomchart SHARED
70
72
  "${CMAKE_CURRENT_SOURCE_DIR}/../cpp/VroomChartHostObject.cpp"
71
73
  "${CMAKE_CURRENT_SOURCE_DIR}/../cpp/VroomFontMgr.cpp"
72
74
  ${VROOM_CORE_SOURCES}
73
- "${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/VroomChartJni.cpp"
75
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/VroomChartJsiBindings.cpp"
76
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/OnLoad.cpp"
74
77
  )
75
78
 
76
79
  target_include_directories(vroomchart PRIVATE
@@ -132,9 +135,16 @@ target_link_options(vroomchart PRIVATE
132
135
  "-Wl,-z,common-page-size=16384"
133
136
  )
134
137
 
138
+ # BindingsInstallerHolder's constructors are compiled into libreactnative.so
139
+ # (RN folds the turbomodulejsijni object library into it), so linking
140
+ # ReactAndroid::reactnative is what resolves the newObjectCxxArgs instantiation
141
+ # in src/main/cpp/VroomChartJsiBindings.cpp. fbjni supplies the JavaClass /
142
+ # registerNatives machinery that wraps it.
135
143
  target_link_libraries(vroomchart
136
144
  ${VROOM_LOG_LIB}
137
145
  ReactAndroid::jsi
146
+ ReactAndroid::reactnative
147
+ fbjni::fbjni
138
148
  shopify_react-native-skia::rnskia
139
149
  vroom_skia_prebuilt
140
150
  )
package/android/README.md CHANGED
@@ -1,12 +1,22 @@
1
1
  # Android bridge
2
2
 
3
3
  Android equivalent of `../ios/`: a small TurboModule
4
- ([`VroomChartModule.kt`](src/main/java/com/vroom/chart/VroomChartModule.kt)) whose
5
- `install()` hands the JSI runtime pointer to a native `vroomchart` library
6
- (built by [`CMakeLists.txt`](CMakeLists.txt)) that installs
7
- `global.VroomChartJSI` via the same platform-agnostic
8
- [`../cpp/VroomJsiInstaller.cpp`](../cpp/VroomJsiInstaller.cpp) the iOS bridge
9
- uses.
4
+ ([`VroomChartModule.kt`](src/main/java/com/vroom/chart/VroomChartModule.kt))
5
+ implementing `TurboModuleWithJSIBindings`. `TurboModuleManager` calls its
6
+ `getBindingsInstaller()` when it instantiates the module and runs the returned
7
+ holder against the JSI runtime, which installs `global.VroomChartJSI` via the
8
+ same platform-agnostic
9
+ [`../cpp/VroomJsiInstaller.cpp`](../cpp/VroomJsiInstaller.cpp) the iOS module
10
+ uses. The native `vroomchart` library (built by
11
+ [`CMakeLists.txt`](CMakeLists.txt)) binds that method in
12
+ [`src/main/cpp/VroomChartJsiBindings.cpp`](src/main/cpp/VroomChartJsiBindings.cpp),
13
+ registered from [`src/main/cpp/OnLoad.cpp`](src/main/cpp/OnLoad.cpp).
14
+
15
+ This replaced an earlier `install()`-driven path that read the runtime pointer
16
+ out of `ReactContext.javaScriptContextHolder`. That still works on current
17
+ React Native, but the bindings hook is the supported entry point and matches
18
+ what iOS now has to do — `[RCTBridge currentBridge]` returns `nil` once
19
+ `RCT_REMOVE_LEGACY_ARCH` is on, which is the default from React Native 0.85.
10
20
 
11
21
  The chart core (`../cpp/_core_src`) and the platform font-manager helper
12
22
  (`../cpp/VroomFontMgr.*`) are unmodified and shared across iOS and Android —
@@ -108,6 +108,16 @@ android {
108
108
  }
109
109
  }
110
110
 
111
+ // prefab copies every consumed module's .so into our intermediates. RN and
112
+ // RN-Skia already ship these in their own AARs, so packaging a second copy
113
+ // here makes app assembly fail on duplicate libraries.
114
+ packagingOptions {
115
+ exclude "**/libc++_shared.so"
116
+ exclude "**/libfbjni.so"
117
+ exclude "**/libjsi.so"
118
+ exclude "**/libreactnative.so"
119
+ }
120
+
111
121
  lintOptions {
112
122
  abortOnError false
113
123
  }
@@ -0,0 +1,10 @@
1
+ #include <jni.h>
2
+
3
+ #include <fbjni/fbjni.h>
4
+
5
+ #include "VroomChartJsiBindings.h"
6
+
7
+ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
8
+ return facebook::jni::initialize(
9
+ vm, [] { vroom::VroomChartJsiBindings::registerNatives(); });
10
+ }
@@ -0,0 +1,35 @@
1
+ #include "VroomChartJsiBindings.h"
2
+
3
+ #include <memory>
4
+
5
+ #include <jsi/jsi.h>
6
+
7
+ #include "VroomJsiInstaller.h"
8
+
9
+ namespace vroom {
10
+
11
+ namespace jni = facebook::jni;
12
+ namespace jsi = facebook::jsi;
13
+ using facebook::react::BindingsInstallerHolder;
14
+ using facebook::react::CallInvoker;
15
+
16
+ void VroomChartJsiBindings::registerNatives() {
17
+ // makeNativeMethod is a macro, so it cannot be namespace-qualified.
18
+ javaClassLocal()->registerNatives({
19
+ makeNativeMethod(
20
+ "getBindingsInstaller",
21
+ VroomChartJsiBindings::getBindingsInstaller),
22
+ });
23
+ }
24
+
25
+ jni::local_ref<BindingsInstallerHolder::javaobject>
26
+ VroomChartJsiBindings::getBindingsInstaller(
27
+ jni::alias_ref<VroomChartJsiBindings> /*jobj*/) {
28
+ // callInvoker is unused: every chart call is synchronous on the JS thread.
29
+ return BindingsInstallerHolder::newObjectCxxArgs(
30
+ [](jsi::Runtime& runtime, const std::shared_ptr<CallInvoker>&) {
31
+ installJsi(runtime);
32
+ });
33
+ }
34
+
35
+ } // namespace vroom
@@ -0,0 +1,27 @@
1
+ #pragma once
2
+
3
+ #include <ReactCommon/BindingsInstallerHolder.h>
4
+ #include <fbjni/fbjni.h>
5
+
6
+ namespace vroom {
7
+
8
+ // Binds com.vroom.chart.VroomChartModule.getBindingsInstaller() (see
9
+ // ../java/com/vroom/chart/VroomChartModule.kt) to C++. The holder it returns
10
+ // is invoked by TurboModuleManager with the JSI runtime, which is where
11
+ // vroom::installJsi() runs. This is the Android counterpart of iOS's
12
+ // -installJSIBindingsWithRuntime:callInvoker: in ../../ios/VroomChartModule.mm.
13
+ class VroomChartJsiBindings
14
+ : public facebook::jni::JavaClass<VroomChartJsiBindings> {
15
+ public:
16
+ static constexpr const char* kJavaDescriptor =
17
+ "Lcom/vroom/chart/VroomChartModule;";
18
+
19
+ static void registerNatives();
20
+
21
+ private:
22
+ static facebook::jni::local_ref<
23
+ facebook::react::BindingsInstallerHolder::javaobject>
24
+ getBindingsInstaller(facebook::jni::alias_ref<VroomChartJsiBindings> jobj);
25
+ };
26
+
27
+ } // namespace vroom
@@ -1,19 +1,24 @@
1
1
  package com.vroom.chart
2
2
 
3
+ import com.facebook.proguard.annotations.DoNotStrip
3
4
  import com.facebook.react.bridge.ReactApplicationContext
4
5
  import com.facebook.react.module.annotations.ReactModule
6
+ import com.facebook.react.turbomodule.core.interfaces.BindingsInstallerHolder
7
+ import com.facebook.react.turbomodule.core.interfaces.TurboModuleWithJSIBindings
5
8
 
6
- // TurboModule counterpart to ../../ios/VroomChartModule.mm. `install()` hands
7
- // the JSI runtime pointer to the native `vroomchart` library (see
8
- // ../../CMakeLists.txt), which installs `global.VroomChartJSI` via the same
9
- // platform-agnostic vroom::installJsi() the iOS bridge calls.
9
+ // TurboModule counterpart to ../../ios/VroomChartModule.mm. TurboModuleManager
10
+ // calls getBindingsInstaller() when it instantiates this module and runs the
11
+ // returned installer against the JSI runtime, which exposes
12
+ // `global.VroomChartJSI` via the same platform-agnostic vroom::installJsi()
13
+ // the iOS module calls. Implemented in ../../cpp/VroomChartJsiBindings.cpp and
14
+ // bound by ../../cpp/OnLoad.cpp.
10
15
  //
11
16
  // `NativeVroomChartSpec` is generated by React Native's codegen from
12
17
  // ../../../src/NativeVroomChart.ts (codegenConfig.android.javaPackageName in
13
18
  // package.json) — the same class name iOS's codegen generates.
14
19
  @ReactModule(name = NativeVroomChartSpec.NAME)
15
20
  class VroomChartModule(reactContext: ReactApplicationContext) :
16
- NativeVroomChartSpec(reactContext) {
21
+ NativeVroomChartSpec(reactContext), TurboModuleWithJSIBindings {
17
22
 
18
23
  companion object {
19
24
  init {
@@ -21,11 +26,10 @@ class VroomChartModule(reactContext: ReactApplicationContext) :
21
26
  }
22
27
  }
23
28
 
24
- private external fun nativeInstall(runtimePointer: Long): Boolean
29
+ @DoNotStrip external override fun getBindingsInstaller(): BindingsInstallerHolder
25
30
 
26
- override fun install(): Boolean {
27
- val runtimePointer =
28
- reactApplicationContext.javaScriptContextHolder?.get() ?: return false
29
- return nativeInstall(runtimePointer)
30
- }
31
+ // Reaching this module from JS means TurboModuleManager already ran the
32
+ // installer above. JS verifies global.VroomChartJSI itself rather than
33
+ // trusting this return value — see ../../../src/useChartCore.ts.
34
+ override fun install(): Boolean = true
31
35
  }
@@ -58,6 +58,10 @@ std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
58
58
  out.push_back(jsi::PropNameID::forAscii(rt, "preservePriceEnvelope"));
59
59
  out.push_back(jsi::PropNameID::forAscii(rt, "beginIntervalMorph"));
60
60
  out.push_back(jsi::PropNameID::forAscii(rt, "beginStreamMorph"));
61
+ out.push_back(jsi::PropNameID::forAscii(rt, "setLoading"));
62
+ out.push_back(jsi::PropNameID::forAscii(rt, "beginLoadingMorph"));
63
+ out.push_back(jsi::PropNameID::forAscii(rt, "setLoadingMorph"));
64
+ out.push_back(jsi::PropNameID::forAscii(rt, "beginLoadingReveal"));
61
65
  out.push_back(jsi::PropNameID::forAscii(rt, "setIntervalMorph"));
62
66
  out.push_back(jsi::PropNameID::forAscii(rt, "pan"));
63
67
  out.push_back(jsi::PropNameID::forAscii(rt, "translate"));
@@ -532,6 +536,83 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
532
536
  });
533
537
  }
534
538
 
539
+ if (name == "setLoading") {
540
+ // setLoading(on, animate?) — show or hide the loading line. `on` must mean
541
+ // "loading *and* holding no data"; the core takes it at face value.
542
+ // `animate` defaults to true; pass false for reduced motion.
543
+ return jsi::Function::createFromHostFunction(
544
+ rt,
545
+ jsi::PropNameID::forAscii(rt, "setLoading"),
546
+ 2,
547
+ [this](jsi::Runtime& /*rt2*/,
548
+ const jsi::Value& /*thisVal*/,
549
+ const jsi::Value* args,
550
+ size_t count) -> jsi::Value {
551
+ if (count < 1) return jsi::Value::undefined();
552
+ const bool on = args[0].isBool() ? args[0].getBool()
553
+ : args[0].asNumber() != 0;
554
+ bool animate = true;
555
+ if (count >= 2 && !args[1].isUndefined() && !args[1].isNull()) {
556
+ animate = args[1].isBool() ? args[1].getBool()
557
+ : args[1].asNumber() != 0;
558
+ }
559
+ vroom_chart_set_loading(chart_, on ? 1 : 0, animate ? 1 : 0);
560
+ return jsi::Value::undefined();
561
+ });
562
+ }
563
+
564
+ if (name == "beginLoadingMorph") {
565
+ // beginLoadingMorph() — hand-off stage one: aim the loading line at the
566
+ // centres of the candles that just landed. Call *after* setCandles, then
567
+ // drive setLoadingMorph from 0 to 1.
568
+ return jsi::Function::createFromHostFunction(
569
+ rt,
570
+ jsi::PropNameID::forAscii(rt, "beginLoadingMorph"),
571
+ 0,
572
+ [this](jsi::Runtime& /*rt2*/,
573
+ const jsi::Value& /*thisVal*/,
574
+ const jsi::Value* /*args*/,
575
+ size_t /*count*/) -> jsi::Value {
576
+ vroom_chart_begin_loading_morph(chart_);
577
+ return jsi::Value::undefined();
578
+ });
579
+ }
580
+
581
+ if (name == "setLoadingMorph") {
582
+ // setLoadingMorph(t) — advance stage one. 0 = the frozen sine, 1 = the
583
+ // polyline through the candle centres.
584
+ return jsi::Function::createFromHostFunction(
585
+ rt,
586
+ jsi::PropNameID::forAscii(rt, "setLoadingMorph"),
587
+ 1,
588
+ [this](jsi::Runtime& /*rt2*/,
589
+ const jsi::Value& /*thisVal*/,
590
+ const jsi::Value* args,
591
+ size_t count) -> jsi::Value {
592
+ if (count < 1) return jsi::Value::undefined();
593
+ vroom_chart_set_loading_morph(
594
+ chart_, static_cast<float>(args[0].asNumber()));
595
+ return jsi::Value::undefined();
596
+ });
597
+ }
598
+
599
+ if (name == "beginLoadingReveal") {
600
+ // beginLoadingReveal() — hand-off stage two: collapse every candle onto the
601
+ // line at zero alpha so it grows outward from there. Call once stage one
602
+ // reaches 1, then drive setIntervalMorph from 0 to 1.
603
+ return jsi::Function::createFromHostFunction(
604
+ rt,
605
+ jsi::PropNameID::forAscii(rt, "beginLoadingReveal"),
606
+ 0,
607
+ [this](jsi::Runtime& /*rt2*/,
608
+ const jsi::Value& /*thisVal*/,
609
+ const jsi::Value* /*args*/,
610
+ size_t /*count*/) -> jsi::Value {
611
+ vroom_chart_begin_loading_reveal(chart_);
612
+ return jsi::Value::undefined();
613
+ });
614
+ }
615
+
535
616
  if (name == "setIntervalMorph") {
536
617
  // setIntervalMorph(t) — advance the capture toward the new candles. `t` is
537
618
  // pre-eased progress: 0 = the captured frame, 1 = settled (capture freed).
@@ -75,6 +75,12 @@ static void ensureAxisTypeface() {
75
75
  }
76
76
 
77
77
  void installJsi(jsi::Runtime& runtime) {
78
+ // Honor the idempotence the header promises: re-running this would replace
79
+ // the global with a fresh `create`, orphaning handles JS already holds.
80
+ if (runtime.global().hasProperty(runtime, "VroomChartJSI")) {
81
+ return;
82
+ }
83
+
78
84
  ensureAxisTypeface();
79
85
 
80
86
  auto create = jsi::Function::createFromHostFunction(
@@ -438,6 +438,9 @@ typedef enum {
438
438
  VROOM_COLOR_ACCENT_BULL, // generic up color: price indicator, volume, MACD
439
439
  VROOM_COLOR_ACCENT_BEAR, // generic down color
440
440
  VROOM_COLOR_LINE, // line-chart-mode close-price polyline
441
+ // The loading line. Its own alpha is honored, then scaled by the fade-in
442
+ // and the stage-3 fade-out, so an opaque color here is the usual choice.
443
+ VROOM_COLOR_SKELETON,
441
444
  VROOM_COLOR_COUNT_
442
445
  } VroomColorKey;
443
446
 
@@ -586,6 +589,44 @@ void vroom_chart_begin_interval_morph(VroomChart* chart, int32_t mode);
586
589
  // translate.
587
590
  void vroom_chart_begin_stream_morph(VroomChart* chart);
588
591
 
592
+ // Shows or hides the loading line: a single stroke drawn across the plot in a
593
+ // slow travelling sine, for a chart that has been laid out but has no data yet.
594
+ // Suppresses the axis text, price badge, crosshair and indicator panes for as
595
+ // long as it's up.
596
+ //
597
+ // `on` must mean "loading *and* holding no data for the series being shown";
598
+ // the core takes it at face value rather than checking the candle buffer,
599
+ // because a host mid-asset-switch can still be holding the previous asset's
600
+ // bars. Pass `animate` 0 for reduced motion: the line draws still, and the
601
+ // chart is allowed to go idle instead of pinning a redraw loop.
602
+ //
603
+ // Passing 0 abandons any hand-off in flight, which is what a failed or
604
+ // superseded fetch wants. To hand off to data instead, use the two stages
605
+ // below.
606
+ void vroom_chart_set_loading(VroomChart* chart, int32_t on, int32_t animate);
607
+
608
+ // Hand-off stage one: reshapes the line into the series. Freezes the sine where
609
+ // it is and aims each vertex at the vertical centre of the candle that will
610
+ // occupy its column, so the line resolves into the silhouette of the data.
611
+ //
612
+ // Call *after* set_candles — it reads them to know where to aim — then drive
613
+ // vroom_chart_set_loading_morph from 0 to 1. The axes and the candles stay
614
+ // suppressed throughout; stage two brings them in. No-op if the line isn't up.
615
+ void vroom_chart_begin_loading_morph(VroomChart* chart);
616
+
617
+ // Advances stage one. `t` (clamped to 0..1) is the eased progress: 0 renders the
618
+ // frozen sine, 1 the polyline through the candle centres.
619
+ void vroom_chart_set_loading_morph(VroomChart* chart, float t);
620
+
621
+ // Hand-off stage two: the candles take over. Captures each one collapsed onto
622
+ // its own vertical centre at zero alpha and leaves the loading state, so the
623
+ // bars grow outward from the line and their color fades up as it fades out.
624
+ //
625
+ // Call once stage one has reached 1, then drive vroom_chart_set_interval_morph
626
+ // from 0 to 1 as usual — the line's fade-out rides that same clock, so the two
627
+ // finish together. No-op if the line isn't up.
628
+ void vroom_chart_begin_loading_reveal(VroomChart* chart);
629
+
589
630
  // Advances the morph started by either begin_*_morph above. `t` (clamped to
590
631
  // 0..1) is the eased progress: 0 renders the captured geometry pixel-identically
591
632
  // to the pre-swap frame, 1 renders the new candles and releases the capture.
@@ -9,6 +9,7 @@
9
9
 
10
10
  #include <algorithm>
11
11
 
12
+ #include "color_lerp.h"
12
13
  #include "theme.h"
13
14
  #include "viewport.h"
14
15
 
@@ -21,18 +22,6 @@ inline uint32_t resolve_color(uint32_t color, uint32_t fill) {
21
22
  return (color >> 24) == 0 ? fill : color;
22
23
  }
23
24
 
24
- // Channel-wise ARGB blend, for a candle that changes direction mid-morph.
25
- inline uint32_t lerp_argb(uint32_t a, uint32_t b, float t) {
26
- uint32_t out = 0;
27
- for (int shift = 0; shift < 32; shift += 8) {
28
- const float ca = static_cast<float>((a >> shift) & 0xFFu);
29
- const float cb = static_cast<float>((b >> shift) & 0xFFu);
30
- const auto v = static_cast<uint32_t>(ca + (cb - ca) * t + 0.5f);
31
- out |= v << shift;
32
- }
33
- return out;
34
- }
35
-
36
25
  inline uint32_t scale_alpha(uint32_t argb, float a) {
37
26
  const auto alpha =
38
27
  static_cast<uint32_t>(static_cast<float>((argb >> 24) & 0xFFu) * a + 0.5f);
@@ -90,6 +79,7 @@ void draw(SkCanvas* canvas,
90
79
 
91
80
  const uint32_t fill_bull = theme.colors[VROOM_COLOR_BULL];
92
81
  const uint32_t fill_bear = theme.colors[VROOM_COLOR_BEAR];
82
+ const uint32_t skeleton_color = theme.colors[VROOM_COLOR_SKELETON];
93
83
 
94
84
  const float body_r = theme.floats[VROOM_FLOAT_CANDLE_RADIUS_PX];
95
85
  const bool wick_round = theme.floats[VROOM_FLOAT_WICK_ROUND_CAP] > 0.5f;
@@ -207,10 +197,21 @@ void draw(SkCanvas* canvas,
207
197
  bool draw_border = bull ? draw_border_bull : draw_border_bear;
208
198
 
209
199
  const bool flip = frm && to && frm->bull != bull;
210
- if (flip || alpha < 0.999f) {
200
+ // A slot handed over from the loading skeleton starts grey, whatever its
201
+ // direction — so it takes the per-slot path from a different origin
202
+ // color than a flip does, and overrides it when both apply.
203
+ const bool from_skeleton = frm && to && frm->skeleton;
204
+ if (flip || from_skeleton || alpha < 0.999f) {
211
205
  const auto blend = [&](uint32_t c_bull, uint32_t c_bear) {
212
206
  uint32_t c = bull ? c_bull : c_bear;
213
- if (flip) c = lerp_argb(bull ? c_bear : c_bull, c, morph_t);
207
+ if (from_skeleton) {
208
+ // lerp_argb covers the alpha channel too, so the bar's
209
+ // opacity rides in on the same blend as its color.
210
+ c = lerp_argb(scale_alpha(skeleton_color, frm->skeleton_alpha),
211
+ c, morph_t);
212
+ } else if (flip) {
213
+ c = lerp_argb(bull ? c_bear : c_bull, c, morph_t);
214
+ }
214
215
  return scale_alpha(c, alpha);
215
216
  };
216
217
  slot_wick = *wick_p;