react-native-vroom-chart 0.4.0 → 0.6.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/android/CMakeLists.txt +143 -0
- package/android/README.md +60 -5
- package/android/build.gradle +115 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/VroomChartJni.cpp +24 -0
- package/android/src/main/java/com/vroom/chart/VroomChartModule.kt +31 -0
- package/android/src/main/java/com/vroom/chart/VroomChartPackage.kt +31 -0
- package/cpp/VroomChartHostObject.cpp +265 -1
- package/cpp/VroomJsiInstaller.cpp +11 -1
- package/cpp/VroomSkiaContext.cpp +15 -7
- package/cpp/_core_include/vroom/vroom_chart.h +95 -0
- package/cpp/_core_src/bollinger.cpp +43 -0
- package/cpp/_core_src/bollinger.h +31 -0
- package/cpp/_core_src/chart.cpp +56 -0
- package/cpp/_core_src/chart.h +45 -0
- package/cpp/_core_src/chart_facade.cpp +117 -0
- package/cpp/_core_src/ma_overlay.cpp +67 -0
- package/cpp/_core_src/ma_overlay.h +21 -0
- package/cpp/_core_src/price_line_layout.cpp +87 -0
- package/cpp/_core_src/price_line_layout.h +80 -0
- package/cpp/_core_src/price_lines.cpp +428 -0
- package/cpp/_core_src/price_lines.h +56 -0
- package/lib/index.d.mts +201 -1
- package/lib/index.d.ts +201 -1
- package/lib/index.js +122 -7
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +122 -7
- package/lib/index.mjs.map +1 -1
- package/package.json +20 -8
- package/react-native-vroom-chart.podspec +1 -1
- package/src/VroomChart.tsx +95 -8
- package/src/index.ts +3 -0
- package/src/jsi.d.ts +68 -0
- package/src/types.ts +3 -0
- package/src/useChartCore.ts +98 -2
package/cpp/_core_src/chart.h
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
#pragma once
|
|
10
10
|
|
|
11
11
|
#include <chrono>
|
|
12
|
+
#include <string>
|
|
12
13
|
#include <vector>
|
|
13
14
|
|
|
14
15
|
#include "vroom/vroom_chart.h"
|
|
@@ -135,6 +136,20 @@ struct VroomChart {
|
|
|
135
136
|
std::vector<unsigned char> vwap_breaks;
|
|
136
137
|
bool vwap_dirty = true;
|
|
137
138
|
|
|
139
|
+
// Bollinger Bands overlay (price pane; no pane reserved). Caches aligned to
|
|
140
|
+
// `candles` (NaN warmup), recomputed lazily by ensure_bollinger() when
|
|
141
|
+
// bollinger_dirty. Defaults: 20-period SMA of close, ±2σ, blue bands /
|
|
142
|
+
// orange basis, 10% fill.
|
|
143
|
+
VroomBollinger bollinger{0, 20, 2.f, 0, 0,
|
|
144
|
+
0xff2962ff, 1.f, // upper: blue
|
|
145
|
+
0xffff6d00, 1.f, // middle: orange
|
|
146
|
+
0xff2962ff, 1.f, // lower: blue
|
|
147
|
+
1, 0.1f};
|
|
148
|
+
std::vector<double> bb_middle_cache;
|
|
149
|
+
std::vector<double> bb_upper_cache;
|
|
150
|
+
std::vector<double> bb_lower_cache;
|
|
151
|
+
bool bollinger_dirty = true;
|
|
152
|
+
|
|
138
153
|
// --- drawings (annotations) --------------------------------------------
|
|
139
154
|
// Committed drawings, anchored in data space so they track the candles on
|
|
140
155
|
// pan/zoom. Drawn on the price pane above candles/overlays.
|
|
@@ -181,6 +196,32 @@ struct VroomChart {
|
|
|
181
196
|
std::vector<VroomBand> bands;
|
|
182
197
|
VroomLiquidityStyle liquidity_style{};
|
|
183
198
|
|
|
199
|
+
// --- price status lines -------------------------------------------------
|
|
200
|
+
// Consumer-supplied horizontal lines at fixed prices (resting orders, TP/SL,
|
|
201
|
+
// liquidation levels), each with a label group and an optional close button.
|
|
202
|
+
//
|
|
203
|
+
// Mirrors the public VroomPriceLine but *owns* its label strings, so the
|
|
204
|
+
// caller may free theirs as soon as the setter returns.
|
|
205
|
+
struct StoredPriceLine {
|
|
206
|
+
double price = 0.0;
|
|
207
|
+
uint32_t color = 0xffef5350;
|
|
208
|
+
float width = 1.f;
|
|
209
|
+
int32_t line_style = 1; // dotted, matching the price indicator
|
|
210
|
+
std::string text;
|
|
211
|
+
std::string quantity;
|
|
212
|
+
int32_t flags = 0; // VroomPriceLineFlags
|
|
213
|
+
};
|
|
214
|
+
std::vector<StoredPriceLine> price_lines;
|
|
215
|
+
VroomPriceLineStyle price_line_style{};
|
|
216
|
+
|
|
217
|
+
// Interaction state, driven by the host's gesture layer. The hovered segment
|
|
218
|
+
// renders highlighted; while a line is dragged it renders at
|
|
219
|
+
// dragged_price_line_price with a ghost at its committed price. -1 = none.
|
|
220
|
+
int32_t hovered_price_line = -1;
|
|
221
|
+
int32_t hovered_price_line_part = -1;
|
|
222
|
+
int32_t dragged_price_line = -1;
|
|
223
|
+
double dragged_price_line_price = 0.0;
|
|
224
|
+
|
|
184
225
|
// --- theme --------------------------------------------------------------
|
|
185
226
|
vroom::Theme theme;
|
|
186
227
|
|
|
@@ -222,6 +263,10 @@ struct VroomChart {
|
|
|
222
263
|
// Recomputes the VWAP cache when vwap_dirty and VWAP is enabled.
|
|
223
264
|
void ensure_vwap();
|
|
224
265
|
|
|
266
|
+
// Recomputes the Bollinger Band caches when bollinger_dirty and the
|
|
267
|
+
// indicator is enabled.
|
|
268
|
+
void ensure_bollinger();
|
|
269
|
+
|
|
225
270
|
// The main drawing pass. Calls into the labels and candles modules.
|
|
226
271
|
void draw_chart(SkCanvas* canvas);
|
|
227
272
|
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
#include "chart.h"
|
|
15
15
|
#include "drawings.h"
|
|
16
16
|
#include "labels.h"
|
|
17
|
+
#include "price_lines.h"
|
|
17
18
|
#include "viewport.h"
|
|
18
19
|
|
|
19
20
|
class SkCanvas;
|
|
@@ -131,6 +132,7 @@ extern "C" void vroom_chart_set_candles(VroomChart* chart, const VroomCandle* da
|
|
|
131
132
|
chart->macd_dirty = true;
|
|
132
133
|
chart->overlays_dirty = true;
|
|
133
134
|
chart->vwap_dirty = true;
|
|
135
|
+
chart->bollinger_dirty = true;
|
|
134
136
|
|
|
135
137
|
// Infer the candle period from the first interval. Robust enough for
|
|
136
138
|
// uniform-duration series (the only kind we model today).
|
|
@@ -158,6 +160,7 @@ extern "C" void vroom_chart_append_candle(VroomChart* chart, const VroomCandle*
|
|
|
158
160
|
chart->macd_dirty = true;
|
|
159
161
|
chart->overlays_dirty = true;
|
|
160
162
|
chart->vwap_dirty = true;
|
|
163
|
+
chart->bollinger_dirty = true;
|
|
161
164
|
chart->mark_dirty();
|
|
162
165
|
}
|
|
163
166
|
|
|
@@ -168,6 +171,7 @@ extern "C" void vroom_chart_update_last(VroomChart* chart, const VroomCandle* c)
|
|
|
168
171
|
chart->macd_dirty = true;
|
|
169
172
|
chart->overlays_dirty = true;
|
|
170
173
|
chart->vwap_dirty = true;
|
|
174
|
+
chart->bollinger_dirty = true;
|
|
171
175
|
chart->mark_dirty();
|
|
172
176
|
}
|
|
173
177
|
|
|
@@ -809,6 +813,70 @@ extern "C" void vroom_chart_set_liquidity(VroomChart* chart,
|
|
|
809
813
|
chart->mark_dirty();
|
|
810
814
|
}
|
|
811
815
|
|
|
816
|
+
// ---- Price status lines ---------------------------------------------------
|
|
817
|
+
|
|
818
|
+
extern "C" void vroom_chart_set_price_lines(VroomChart* chart,
|
|
819
|
+
const VroomPriceLine* lines,
|
|
820
|
+
size_t count,
|
|
821
|
+
const VroomPriceLineStyle* style) {
|
|
822
|
+
if (!chart) return;
|
|
823
|
+
// Deep-copy into the owning form: the label strings live in the chart, so the
|
|
824
|
+
// caller's buffers may be freed as soon as this returns.
|
|
825
|
+
chart->price_lines.clear();
|
|
826
|
+
chart->price_lines.reserve(count);
|
|
827
|
+
for (size_t i = 0; i < count; ++i) {
|
|
828
|
+
const VroomPriceLine& src = lines[i];
|
|
829
|
+
VroomChart::StoredPriceLine pl;
|
|
830
|
+
pl.price = src.price;
|
|
831
|
+
pl.color = src.color;
|
|
832
|
+
pl.width = src.width;
|
|
833
|
+
pl.line_style = src.line_style;
|
|
834
|
+
if (src.text) pl.text = src.text;
|
|
835
|
+
if (src.quantity) pl.quantity = src.quantity;
|
|
836
|
+
pl.flags = src.flags;
|
|
837
|
+
chart->price_lines.push_back(std::move(pl));
|
|
838
|
+
}
|
|
839
|
+
if (style) chart->price_line_style = *style;
|
|
840
|
+
// Drop interaction state that no longer refers to a live line — otherwise a
|
|
841
|
+
// removed line would leave a stuck hover highlight or drag preview.
|
|
842
|
+
const auto n = static_cast<int32_t>(count);
|
|
843
|
+
if (chart->hovered_price_line >= n) {
|
|
844
|
+
chart->hovered_price_line = -1;
|
|
845
|
+
chart->hovered_price_line_part = -1;
|
|
846
|
+
}
|
|
847
|
+
if (chart->dragged_price_line >= n) chart->dragged_price_line = -1;
|
|
848
|
+
chart->mark_dirty();
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
extern "C" void vroom_chart_set_price_line_hover(VroomChart* chart, int32_t index,
|
|
852
|
+
int32_t part) {
|
|
853
|
+
if (!chart) return;
|
|
854
|
+
if (index < 0 || index >= static_cast<int32_t>(chart->price_lines.size())) {
|
|
855
|
+
index = -1;
|
|
856
|
+
part = -1;
|
|
857
|
+
}
|
|
858
|
+
if (chart->hovered_price_line == index && chart->hovered_price_line_part == part) {
|
|
859
|
+
return; // hover fires on every pointer move; don't redraw for nothing
|
|
860
|
+
}
|
|
861
|
+
chart->hovered_price_line = index;
|
|
862
|
+
chart->hovered_price_line_part = part;
|
|
863
|
+
chart->mark_dirty();
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
extern "C" void vroom_chart_set_price_line_drag(VroomChart* chart, int32_t index,
|
|
867
|
+
double price) {
|
|
868
|
+
if (!chart) return;
|
|
869
|
+
if (index < 0 || index >= static_cast<int32_t>(chart->price_lines.size())) {
|
|
870
|
+
if (chart->dragged_price_line == -1) return;
|
|
871
|
+
chart->dragged_price_line = -1;
|
|
872
|
+
chart->mark_dirty();
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
chart->dragged_price_line = index;
|
|
876
|
+
chart->dragged_price_line_price = price;
|
|
877
|
+
chart->mark_dirty();
|
|
878
|
+
}
|
|
879
|
+
|
|
812
880
|
extern "C" void vroom_chart_set_draft(VroomChart* chart, int64_t a_time,
|
|
813
881
|
double a_price, bool has_b, int64_t b_time,
|
|
814
882
|
double b_price, bool guide, uint32_t color,
|
|
@@ -923,6 +991,32 @@ extern "C" bool vroom_chart_hit_test_drawing(VroomChart* chart, float x_px,
|
|
|
923
991
|
return true;
|
|
924
992
|
}
|
|
925
993
|
|
|
994
|
+
extern "C" bool vroom_chart_hit_test_price_line(VroomChart* chart, float x_px,
|
|
995
|
+
float y_px, int32_t* out_index,
|
|
996
|
+
int32_t* out_part) {
|
|
997
|
+
if (!chart || chart->price_lines.empty() || chart->candles.empty()) return false;
|
|
998
|
+
const auto lay = chart->layout();
|
|
999
|
+
// Same bounds and pane geometry as draw_chart, so the grab regions line up
|
|
1000
|
+
// with what's on screen.
|
|
1001
|
+
const auto range = vroom::visible_indices(
|
|
1002
|
+
chart->candles.data(), chart->candles.size(),
|
|
1003
|
+
chart->visible_start_ms, chart->visible_end_ms);
|
|
1004
|
+
const size_t n = range.end - range.start;
|
|
1005
|
+
const auto bounds =
|
|
1006
|
+
chart->price_bounds_manual
|
|
1007
|
+
? chart->price_bounds
|
|
1008
|
+
: vroom::auto_price_bounds(chart->candles.data() + range.start, n);
|
|
1009
|
+
const float candle_area_h = vroom::price_pane_bottom(lay);
|
|
1010
|
+
const float candle_right =
|
|
1011
|
+
chart->width_px - lay.y_axis_width_px - lay.right_padding_px;
|
|
1012
|
+
const auto hit = vroom::price_lines::hit_test(*chart, lay, bounds, candle_right,
|
|
1013
|
+
candle_area_h, x_px, y_px);
|
|
1014
|
+
if (hit.index < 0) return false;
|
|
1015
|
+
if (out_index) *out_index = hit.index;
|
|
1016
|
+
if (out_part) *out_part = hit.part;
|
|
1017
|
+
return true;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
926
1020
|
extern "C" void vroom_chart_set_selected_drawing(VroomChart* chart,
|
|
927
1021
|
int32_t index,
|
|
928
1022
|
int32_t grabbed_endpoint) {
|
|
@@ -1049,6 +1143,29 @@ extern "C" void vroom_chart_set_vwap(VroomChart* chart, bool enabled,
|
|
|
1049
1143
|
chart->mark_dirty();
|
|
1050
1144
|
}
|
|
1051
1145
|
|
|
1146
|
+
extern "C" void vroom_chart_set_bollinger(VroomChart* chart,
|
|
1147
|
+
const VroomBollinger* cfg) {
|
|
1148
|
+
if (!chart || !cfg) return;
|
|
1149
|
+
VroomBollinger next = *cfg;
|
|
1150
|
+
next.enabled = next.enabled ? 1 : 0;
|
|
1151
|
+
next.fill_enabled = next.fill_enabled ? 1 : 0;
|
|
1152
|
+
if (next.period < 1) next.period = 1;
|
|
1153
|
+
if (!(next.mult >= 0.f)) next.mult = 0.f;
|
|
1154
|
+
next.fill_opacity = std::clamp(next.fill_opacity, 0.f, 1.f);
|
|
1155
|
+
|
|
1156
|
+
// Only the series-affecting fields force a recompute; style and fill
|
|
1157
|
+
// changes are render-only.
|
|
1158
|
+
const VroomBollinger& cur = chart->bollinger;
|
|
1159
|
+
const bool recompute = cur.enabled != next.enabled ||
|
|
1160
|
+
cur.period != next.period ||
|
|
1161
|
+
cur.mult != next.mult ||
|
|
1162
|
+
cur.source != next.source ||
|
|
1163
|
+
cur.basis_kind != next.basis_kind;
|
|
1164
|
+
chart->bollinger = next;
|
|
1165
|
+
if (recompute) chart->bollinger_dirty = true;
|
|
1166
|
+
chart->mark_dirty();
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1052
1169
|
// ---- Direct draw (used by hosts that don't need the SkPicture cache) ------
|
|
1053
1170
|
|
|
1054
1171
|
extern "C" void vroom_chart_draw(VroomChart* chart, SkCanvas* canvas) {
|
|
@@ -78,4 +78,71 @@ void draw(SkCanvas* canvas,
|
|
|
78
78
|
canvas->restore();
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
void fill_between(SkCanvas* canvas,
|
|
82
|
+
const Layout& lay,
|
|
83
|
+
const PriceBounds& bounds,
|
|
84
|
+
const ::VroomCandle* visible,
|
|
85
|
+
std::size_t n,
|
|
86
|
+
const double* upper_visible,
|
|
87
|
+
const double* lower_visible,
|
|
88
|
+
int64_t window_ms,
|
|
89
|
+
int64_t visible_start_ms,
|
|
90
|
+
int64_t candle_duration_ms,
|
|
91
|
+
float candle_right,
|
|
92
|
+
float candle_area_h,
|
|
93
|
+
uint32_t color,
|
|
94
|
+
float opacity) {
|
|
95
|
+
if (!canvas || !upper_visible || !lower_visible || n == 0 ||
|
|
96
|
+
candle_right <= 0.f || candle_area_h <= 0.f) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
opacity = std::clamp(opacity, 0.f, 1.f);
|
|
100
|
+
if (opacity <= 0.f) return;
|
|
101
|
+
|
|
102
|
+
// One closed contour per maximal run where both series are finite; a
|
|
103
|
+
// single-point run has no area. Multiple runs (e.g. around a data gap)
|
|
104
|
+
// become multiple contours in one path.
|
|
105
|
+
SkPathBuilder path;
|
|
106
|
+
std::size_t i = 0;
|
|
107
|
+
while (i < n) {
|
|
108
|
+
if (!std::isfinite(upper_visible[i]) ||
|
|
109
|
+
!std::isfinite(lower_visible[i])) {
|
|
110
|
+
++i;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
std::size_t e = i;
|
|
114
|
+
while (e + 1 < n && std::isfinite(upper_visible[e + 1]) &&
|
|
115
|
+
std::isfinite(lower_visible[e + 1])) {
|
|
116
|
+
++e;
|
|
117
|
+
}
|
|
118
|
+
if (e > i) {
|
|
119
|
+
const auto x_at = [&](std::size_t k) {
|
|
120
|
+
return vroom::candle_center_x(lay, visible[k].time_ms,
|
|
121
|
+
candle_duration_ms,
|
|
122
|
+
visible_start_ms, window_ms);
|
|
123
|
+
};
|
|
124
|
+
path.moveTo(x_at(i), vroom::price_to_y(lay, bounds, upper_visible[i]));
|
|
125
|
+
for (std::size_t k = i + 1; k <= e; ++k) {
|
|
126
|
+
path.lineTo(x_at(k), vroom::price_to_y(lay, bounds, upper_visible[k]));
|
|
127
|
+
}
|
|
128
|
+
for (std::size_t k = e + 1; k-- > i;) {
|
|
129
|
+
path.lineTo(x_at(k), vroom::price_to_y(lay, bounds, lower_visible[k]));
|
|
130
|
+
}
|
|
131
|
+
path.close();
|
|
132
|
+
}
|
|
133
|
+
i = e + 1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
SkPaint fill;
|
|
137
|
+
fill.setAntiAlias(true);
|
|
138
|
+
fill.setColor(static_cast<SkColor>(color));
|
|
139
|
+
fill.setAlphaf(fill.getAlphaf() * opacity);
|
|
140
|
+
fill.setStyle(SkPaint::kFill_Style);
|
|
141
|
+
|
|
142
|
+
canvas->save();
|
|
143
|
+
canvas->clipRect(SkRect::MakeLTRB(0.f, 0.f, candle_right, candle_area_h));
|
|
144
|
+
canvas->drawPath(path.detach(), fill);
|
|
145
|
+
canvas->restore();
|
|
146
|
+
}
|
|
147
|
+
|
|
81
148
|
} // namespace vroom::ma_overlay
|
|
@@ -40,4 +40,25 @@ void draw(SkCanvas* canvas,
|
|
|
40
40
|
const unsigned char* break_before = nullptr,
|
|
41
41
|
float opacity = 1.f);
|
|
42
42
|
|
|
43
|
+
// Fills the closed region between two aligned series (NaN where undefined)
|
|
44
|
+
// with `color` at its alpha × `opacity` — used for the Bollinger Band fill.
|
|
45
|
+
// Runs where either series is NaN are skipped, so the fill never bridges the
|
|
46
|
+
// warmup gap. Plain-alpha SkPaint fill, no gradient shader (the Skia gradient
|
|
47
|
+
// APIs diverge across our pinned versions; see liquidity.cpp). Clipped to the
|
|
48
|
+
// candle area like draw().
|
|
49
|
+
void fill_between(SkCanvas* canvas,
|
|
50
|
+
const Layout& lay,
|
|
51
|
+
const PriceBounds& bounds,
|
|
52
|
+
const ::VroomCandle* visible,
|
|
53
|
+
std::size_t n,
|
|
54
|
+
const double* upper_visible,
|
|
55
|
+
const double* lower_visible,
|
|
56
|
+
int64_t window_ms,
|
|
57
|
+
int64_t visible_start_ms,
|
|
58
|
+
int64_t candle_duration_ms,
|
|
59
|
+
float candle_right,
|
|
60
|
+
float candle_area_h,
|
|
61
|
+
uint32_t color,
|
|
62
|
+
float opacity);
|
|
63
|
+
|
|
43
64
|
} // namespace vroom::ma_overlay
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#include "price_line_layout.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
|
|
6
|
+
namespace vroom::price_lines {
|
|
7
|
+
|
|
8
|
+
namespace {
|
|
9
|
+
// A pill's width is its text plus symmetric padding. Zero-width text means the
|
|
10
|
+
// segment is absent, not a padding-only sliver.
|
|
11
|
+
float pill_width(float text_w) {
|
|
12
|
+
return text_w > 0.f ? text_w + 2.f * kPadH : 0.f;
|
|
13
|
+
}
|
|
14
|
+
} // namespace
|
|
15
|
+
|
|
16
|
+
bool contains(const Rect& r, float x, float y) {
|
|
17
|
+
if (r.empty()) return false;
|
|
18
|
+
return x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
GroupLayout layout_group(const LabelMetrics& metrics,
|
|
22
|
+
float y,
|
|
23
|
+
float pane_right,
|
|
24
|
+
const ::VroomPriceLineStyle& style) {
|
|
25
|
+
GroupLayout out;
|
|
26
|
+
|
|
27
|
+
const float body_w = pill_width(metrics.text_w);
|
|
28
|
+
const float qty_w = pill_width(metrics.quantity_w);
|
|
29
|
+
// The close button is a square cell, so its icon stays centered whatever the
|
|
30
|
+
// font size works out to.
|
|
31
|
+
const float close_w = metrics.closable ? metrics.label_h : 0.f;
|
|
32
|
+
const float group_w = body_w + qty_w + close_w;
|
|
33
|
+
|
|
34
|
+
if (group_w <= 0.f || metrics.label_h <= 0.f || pane_right <= 0.f) {
|
|
35
|
+
// A bare line: no group, but still report a degenerate span at the
|
|
36
|
+
// anchor so callers can treat left/right uniformly.
|
|
37
|
+
out.left = out.right = pane_right;
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
float left = 0.f;
|
|
42
|
+
switch (style.align) {
|
|
43
|
+
case 0: // left
|
|
44
|
+
left = 0.f;
|
|
45
|
+
break;
|
|
46
|
+
case 1: // center
|
|
47
|
+
left = (pane_right - group_w) * 0.5f;
|
|
48
|
+
break;
|
|
49
|
+
default: { // right
|
|
50
|
+
const float frac = std::clamp(style.line_length_frac, 0.f, 1.f);
|
|
51
|
+
const float right = pane_right - frac * pane_right - kAxisGutter;
|
|
52
|
+
left = right - group_w;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Keep the group on-screen: never past the axis, never off the left edge.
|
|
57
|
+
left = std::min(left, pane_right - group_w);
|
|
58
|
+
left = std::max(left, 0.f);
|
|
59
|
+
|
|
60
|
+
const float top = y - metrics.label_h * 0.5f;
|
|
61
|
+
const float bottom = top + metrics.label_h;
|
|
62
|
+
|
|
63
|
+
float x = left;
|
|
64
|
+
if (body_w > 0.f) {
|
|
65
|
+
out.body = Rect{x, top, x + body_w, bottom};
|
|
66
|
+
x += body_w;
|
|
67
|
+
}
|
|
68
|
+
if (qty_w > 0.f) {
|
|
69
|
+
out.quantity = Rect{x, top, x + qty_w, bottom};
|
|
70
|
+
x += qty_w;
|
|
71
|
+
}
|
|
72
|
+
if (close_w > 0.f) {
|
|
73
|
+
out.close = Rect{x, top, x + close_w, bottom};
|
|
74
|
+
x += close_w;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
out.left = left;
|
|
78
|
+
out.right = x;
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
bool hits_line(float line_y, float stroke_width, float y) {
|
|
83
|
+
const float band = std::max(stroke_width, 1.f) + kLineHitTolerance;
|
|
84
|
+
return std::fabs(y - line_y) <= band;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
} // namespace vroom::price_lines
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Price-status-line geometry — pure functions, no Skia, no state.
|
|
2
|
+
//
|
|
3
|
+
// Split out from price_lines.cpp so the rendering pass and the hit-test pass
|
|
4
|
+
// derive their rectangles from the same arithmetic: a label the user can see but
|
|
5
|
+
// not click (or vice versa) is the classic failure mode for this widget. Being
|
|
6
|
+
// Skia-free it also unit-tests without a Skia checkout.
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include "vroom/vroom_chart.h"
|
|
11
|
+
namespace vroom::price_lines {
|
|
12
|
+
|
|
13
|
+
// Pill padding / corner radius, matching the current-price indicator so a price
|
|
14
|
+
// line and the price badge read as the same family of chrome.
|
|
15
|
+
constexpr float kPadH = 8.f; // left/right of the text
|
|
16
|
+
constexpr float kPadV = 4.f; // above/below the text
|
|
17
|
+
constexpr float kCorner = 6.f; // rounded-pill corner radius
|
|
18
|
+
|
|
19
|
+
// Extra grab band above and below the stroke, so a 1px line is still an easy
|
|
20
|
+
// target. Matches the tolerance TradingView's own chart libraries settled on.
|
|
21
|
+
constexpr float kLineHitTolerance = 7.f;
|
|
22
|
+
|
|
23
|
+
// Gap between the label group and the price axis when right-aligned. Wide enough
|
|
24
|
+
// that the line still shows through it — a couple of dashes reading as "this
|
|
25
|
+
// continues to the badge" rather than one orphaned dot.
|
|
26
|
+
constexpr float kAxisGutter = 12.f;
|
|
27
|
+
|
|
28
|
+
struct Rect {
|
|
29
|
+
float left = 0.f;
|
|
30
|
+
float top = 0.f;
|
|
31
|
+
float right = 0.f;
|
|
32
|
+
float bottom = 0.f;
|
|
33
|
+
|
|
34
|
+
// Empty rects stand in for absent segments (no quantity, no close button).
|
|
35
|
+
bool empty() const { return right <= left || bottom <= top; }
|
|
36
|
+
float width() const { return right - left; }
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// True when (x, y) falls inside `r`. Always false for an empty rect.
|
|
40
|
+
bool contains(const Rect& r, float x, float y);
|
|
41
|
+
|
|
42
|
+
// The text extents a caller measured with the real font, plus which optional
|
|
43
|
+
// segments to reserve room for. `label_h` is the pill height (already including
|
|
44
|
+
// kPadV on both sides).
|
|
45
|
+
struct LabelMetrics {
|
|
46
|
+
float text_w = 0.f; // body text width; 0 hides the body pill
|
|
47
|
+
float quantity_w = 0.f; // quantity text width; 0 hides the quantity pill
|
|
48
|
+
float label_h = 0.f;
|
|
49
|
+
bool closable = false; // reserve the trailing square close-button cell
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// The laid-out label group, vertically centered on the line. Any of the three
|
|
53
|
+
// segment rects may be empty. `left`/`right` bound whichever segments exist and
|
|
54
|
+
// are equal when the group is empty (a bare line).
|
|
55
|
+
struct GroupLayout {
|
|
56
|
+
Rect body;
|
|
57
|
+
Rect quantity;
|
|
58
|
+
Rect close;
|
|
59
|
+
float left = 0.f;
|
|
60
|
+
float right = 0.f;
|
|
61
|
+
|
|
62
|
+
bool empty() const { return right <= left; }
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Lays the group out on the line at `y`. `pane_right` is the inner edge of the
|
|
66
|
+
// price axis (the line's right end).
|
|
67
|
+
//
|
|
68
|
+
// `style.align` picks the anchor: 2 (right, the default) parks the group's right
|
|
69
|
+
// edge `style.line_length_frac` of the pane width in from the axis; 1 centers
|
|
70
|
+
// it; 0 pins it to the left edge. The group is clamped into [0, pane_right], so
|
|
71
|
+
// a label wider than the pane starts flush left rather than overflowing.
|
|
72
|
+
GroupLayout layout_group(const LabelMetrics& metrics,
|
|
73
|
+
float y,
|
|
74
|
+
float pane_right,
|
|
75
|
+
const ::VroomPriceLineStyle& style);
|
|
76
|
+
|
|
77
|
+
// True when `y` is within the grab band of a line drawn at `line_y`.
|
|
78
|
+
bool hits_line(float line_y, float stroke_width, float y);
|
|
79
|
+
|
|
80
|
+
} // namespace vroom::price_lines
|