react-native-vroom-chart 0.5.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 +218 -1
- package/cpp/VroomJsiInstaller.cpp +11 -1
- package/cpp/VroomSkiaContext.cpp +15 -7
- package/cpp/_core_include/vroom/vroom_chart.h +70 -0
- package/cpp/_core_src/chart.cpp +7 -0
- package/cpp/_core_src/chart.h +27 -0
- package/cpp/_core_src/chart_facade.cpp +91 -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 +104 -1
- package/lib/index.d.ts +104 -1
- package/lib/index.js +98 -7
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +98 -7
- package/lib/index.mjs.map +1 -1
- package/package.json +6 -5
- package/src/VroomChart.tsx +93 -8
- package/src/index.ts +2 -0
- package/src/jsi.d.ts +48 -0
- package/src/types.ts +2 -0
- package/src/useChartCore.ts +65 -3
|
@@ -40,7 +40,17 @@ static void ensureAxisTypeface(jsi::Runtime& runtime) {
|
|
|
40
40
|
if (!ctx) return;
|
|
41
41
|
auto mgr = ctx->createFontMgr();
|
|
42
42
|
if (!mgr) return;
|
|
43
|
-
|
|
43
|
+
|
|
44
|
+
// A null family name is meant to request "the platform default" — CoreText
|
|
45
|
+
// (iOS) honors that, but Android's SkFontMgr_New_Android does not: it
|
|
46
|
+
// returns null unless given an actual family name, even though its
|
|
47
|
+
// underlying font set (sans-serif, arial, ...) is perfectly populated. Fall
|
|
48
|
+
// back to "sans-serif" (Android's standard generic-family alias) whenever
|
|
49
|
+
// the null-family lookup comes back empty.
|
|
50
|
+
auto tf = mgr->matchFamilyStyle(nullptr, SkFontStyle());
|
|
51
|
+
if (!tf) {
|
|
52
|
+
tf = mgr->matchFamilyStyle("sans-serif", SkFontStyle());
|
|
53
|
+
}
|
|
44
54
|
if (!tf) return;
|
|
45
55
|
vroom::set_axis_typeface(tf);
|
|
46
56
|
done = true;
|
package/cpp/VroomSkiaContext.cpp
CHANGED
|
@@ -12,13 +12,21 @@ std::shared_ptr<RNSkia::RNSkPlatformContext> getRNSkContext(
|
|
|
12
12
|
if (!obj.isHostObject(runtime)) return nullptr;
|
|
13
13
|
auto host = obj.asHostObject(runtime);
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
15
|
+
// `global.SkiaApi` is always installed by RN-Skia's own
|
|
16
|
+
// RNSkManager::installBindings() as exactly a `JsiSkApi` host object — never
|
|
17
|
+
// any other JsiSkHostObject subclass — so this cast is safe as a plain
|
|
18
|
+
// static_cast, without any RTTI check. We deliberately avoid
|
|
19
|
+
// dynamic_pointer_cast here: on Android, RN-Skia is compiled into its own
|
|
20
|
+
// librnskia.so, separate from libvroomchart.so, so a `JsiSkApi` constructed
|
|
21
|
+
// by librnskia.so's code carries typeinfo from that .so; a dynamic_cast
|
|
22
|
+
// performed here (in libvroomchart.so) sees an unmerged RTTI record for
|
|
23
|
+
// "the same" class and always fails, even though the object is perfectly
|
|
24
|
+
// valid to use. (Same underlying issue as the JsiSkPicture cross-.so cast
|
|
25
|
+
// documented in VroomChartHostObject.cpp — but there RN-Skia's own compiled
|
|
26
|
+
// code does the cast on an object *we* construct, so we can't avoid it;
|
|
27
|
+
// here *we* do the cast on an object *they* construct, so we can just skip
|
|
28
|
+
// the runtime check we don't need.)
|
|
29
|
+
auto* accessor = static_cast<HostObjectAccessor*>(host.get());
|
|
22
30
|
return accessor->getContext();
|
|
23
31
|
}
|
|
24
32
|
|
|
@@ -118,6 +118,44 @@ typedef struct VroomLiquidityStyle {
|
|
|
118
118
|
float width_frac;
|
|
119
119
|
} VroomLiquidityStyle;
|
|
120
120
|
|
|
121
|
+
// ---- Price status lines ---------------------------------------------------
|
|
122
|
+
|
|
123
|
+
// Bit flags for VroomPriceLine::flags.
|
|
124
|
+
typedef enum {
|
|
125
|
+
VROOM_PRICE_LINE_DRAGGABLE = 1 << 0, // the line can be dragged vertically
|
|
126
|
+
VROOM_PRICE_LINE_CLOSABLE = 1 << 1, // render the trailing close ("x") button
|
|
127
|
+
VROOM_PRICE_LINE_AXIS_LABEL = 1 << 2, // render the price badge in the y-axis strip
|
|
128
|
+
VROOM_PRICE_LINE_EXTEND_LEFT = 1 << 3, // extend the line to the pane's left edge
|
|
129
|
+
} VroomPriceLineFlags;
|
|
130
|
+
|
|
131
|
+
// A consumer-supplied horizontal status line at a fixed price — the primitive
|
|
132
|
+
// behind resting limit orders, take-profits, liquidation levels and the like.
|
|
133
|
+
//
|
|
134
|
+
// Visually: a line across the price pane ending in a label group made of a body
|
|
135
|
+
// pill (`text`), an optional solid-filled `quantity` pill, and an optional close
|
|
136
|
+
// button, plus an optional price badge in the y-axis strip.
|
|
137
|
+
//
|
|
138
|
+
// `text` / `quantity` are UTF-8 and copied internally, so the caller may free
|
|
139
|
+
// them as soon as the call returns. A null or empty string hides that segment.
|
|
140
|
+
typedef struct VroomPriceLine {
|
|
141
|
+
double price;
|
|
142
|
+
uint32_t color; // 0xAARRGGBB — line, border, body text, close icon
|
|
143
|
+
float width; // stroke width in px
|
|
144
|
+
int32_t line_style; // 0 = solid, 1 = dotted, 2 = dashed
|
|
145
|
+
const char* text; // body label
|
|
146
|
+
const char* quantity; // trailing solid-fill segment
|
|
147
|
+
int32_t flags; // bitwise-or of VroomPriceLineFlags
|
|
148
|
+
} VroomPriceLine;
|
|
149
|
+
|
|
150
|
+
// Layout/style shared by every price line, so the per-line struct stays small.
|
|
151
|
+
typedef struct VroomPriceLineStyle {
|
|
152
|
+
uint32_t body_bg; // translucent body/close-button pill fill (0xAARRGGBB)
|
|
153
|
+
float font_size_px; // 0 = inherit VROOM_FLOAT_AXIS_FONT_SIZE_PX
|
|
154
|
+
float line_length_frac; // 0..1 of pane width: the label group's right-edge inset
|
|
155
|
+
int32_t align; // 0 = left, 1 = center, 2 = right
|
|
156
|
+
float hover_boost; // brightness multiplier for the hovered segment (1 = flat)
|
|
157
|
+
} VroomPriceLineStyle;
|
|
158
|
+
|
|
121
159
|
// A continuous data coordinate at a pixel position (no candle snapping). Used to
|
|
122
160
|
// translate a drawing-tool click into a data-space anchor.
|
|
123
161
|
typedef struct VroomCoord {
|
|
@@ -390,6 +428,38 @@ void vroom_chart_translate_drawing(VroomChart* chart, int32_t index,
|
|
|
390
428
|
void vroom_chart_set_liquidity(VroomChart* chart, const VroomBand* bands,
|
|
391
429
|
size_t count, const VroomLiquidityStyle* style);
|
|
392
430
|
|
|
431
|
+
// ---- Price status lines ---------------------------------------------------
|
|
432
|
+
|
|
433
|
+
// Replaces the full set of price status lines and their shared style. Lines
|
|
434
|
+
// render on the price pane above the axis labels (so their badges cover any
|
|
435
|
+
// label they overlap) and below the crosshair. A line whose price maps outside
|
|
436
|
+
// the price pane is skipped rather than clamped. Pass count 0 to clear;
|
|
437
|
+
// `style` may be null when count is 0.
|
|
438
|
+
void vroom_chart_set_price_lines(VroomChart* chart, const VroomPriceLine* lines,
|
|
439
|
+
size_t count,
|
|
440
|
+
const VroomPriceLineStyle* style);
|
|
441
|
+
|
|
442
|
+
// Hit-tests pixel (x_px, y_px) against the price lines. On a hit, fills
|
|
443
|
+
// *out_index with the line index and *out_part with 0 (the line or its label
|
|
444
|
+
// body — the drag target) or 1 (the close button), and returns true. When
|
|
445
|
+
// several lines are within tolerance the nearest in y wins. Only draggable
|
|
446
|
+
// lines report part 0 and only closable lines report part 1. Returns false on a
|
|
447
|
+
// miss (out params untouched). Either out pointer may be null.
|
|
448
|
+
bool vroom_chart_hit_test_price_line(VroomChart* chart, float x_px, float y_px,
|
|
449
|
+
int32_t* out_index, int32_t* out_part);
|
|
450
|
+
|
|
451
|
+
// Marks a price line's segment as hovered so it renders highlighted. `index` -1
|
|
452
|
+
// clears the hover. `part` matches vroom_chart_hit_test_price_line.
|
|
453
|
+
void vroom_chart_set_price_line_hover(VroomChart* chart, int32_t index,
|
|
454
|
+
int32_t part);
|
|
455
|
+
|
|
456
|
+
// Drives the live drag preview: the line, its label and its badge render at
|
|
457
|
+
// `price` instead of the committed one, and a faint ghost marks where it
|
|
458
|
+
// started. `index` -1 ends the preview. The committed price is never mutated —
|
|
459
|
+
// the host applies (or rejects) the new price by restating its lines.
|
|
460
|
+
void vroom_chart_set_price_line_drag(VroomChart* chart, int32_t index,
|
|
461
|
+
double price);
|
|
462
|
+
|
|
393
463
|
// Sets the transient in-progress "draft" the drawing tool shows while the user
|
|
394
464
|
// places points. Node A is always shown; when `has_b`, node B is shown too.
|
|
395
465
|
// `guide != 0` also draws the live preview (a guideline for a line, or a preview
|
package/cpp/_core_src/chart.cpp
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
#include "macd.h"
|
|
31
31
|
#include "macd_pane.h"
|
|
32
32
|
#include "price_indicator.h"
|
|
33
|
+
#include "price_lines.h"
|
|
33
34
|
#include "rsi.h"
|
|
34
35
|
#include "rsi_pane.h"
|
|
35
36
|
#include "volume.h"
|
|
@@ -279,6 +280,12 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
279
280
|
vroom::price_indicator::draw(canvas, *this, lay, bounds,
|
|
280
281
|
candle_right, candle_area_h);
|
|
281
282
|
|
|
283
|
+
// 7.55. Consumer-supplied price status lines — same tier as the current-price
|
|
284
|
+
// indicator (their badges must cover the labels underneath), but after
|
|
285
|
+
// it so a resting order at the last close stays readable.
|
|
286
|
+
vroom::price_lines::draw(canvas, *this, lay, bounds, candle_right,
|
|
287
|
+
candle_area_h);
|
|
288
|
+
|
|
282
289
|
// 7.6. Indicator panes stacked below the candles, ordered by enable
|
|
283
290
|
// sequence (most recently enabled at the bottom). Each pane is
|
|
284
291
|
// INDICATOR_HEIGHT_FRAC of the height; the candle pane already shrank
|
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"
|
|
@@ -195,6 +196,32 @@ struct VroomChart {
|
|
|
195
196
|
std::vector<VroomBand> bands;
|
|
196
197
|
VroomLiquidityStyle liquidity_style{};
|
|
197
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
|
+
|
|
198
225
|
// --- theme --------------------------------------------------------------
|
|
199
226
|
vroom::Theme theme;
|
|
200
227
|
|
|
@@ -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;
|
|
@@ -812,6 +813,70 @@ extern "C" void vroom_chart_set_liquidity(VroomChart* chart,
|
|
|
812
813
|
chart->mark_dirty();
|
|
813
814
|
}
|
|
814
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
|
+
|
|
815
880
|
extern "C" void vroom_chart_set_draft(VroomChart* chart, int64_t a_time,
|
|
816
881
|
double a_price, bool has_b, int64_t b_time,
|
|
817
882
|
double b_price, bool guide, uint32_t color,
|
|
@@ -926,6 +991,32 @@ extern "C" bool vroom_chart_hit_test_drawing(VroomChart* chart, float x_px,
|
|
|
926
991
|
return true;
|
|
927
992
|
}
|
|
928
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
|
+
|
|
929
1020
|
extern "C" void vroom_chart_set_selected_drawing(VroomChart* chart,
|
|
930
1021
|
int32_t index,
|
|
931
1022
|
int32_t grabbed_endpoint) {
|
|
@@ -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
|