refactor(render): share the readout panel and swatch drawing with the legend

This commit is contained in:
Martino Ferrari committed 2026-09-28 19:25:33 +02:00
1 parent 29824b6919
commit 10d5129fbf
2 files changed
+54 -56

No files matched your search

+12
View File
@@ -487,6 +487,18 @@ class Renderer {
/// them again, so the panel a test asserts and the panel this draws are the same /// them again, so the panel a test asserts and the panel this draws are the same
/// numbers. /// numbers.
void draw_legend(const Plot& plot, const PlotLayout& layout); void draw_legend(const Plot& plot, const PlotLayout& layout);
/// The backplate the legend and the crosshair's readout share: the frame's background at
/// `kLegendPanelAlpha` over `panel`, with a one-device-pixel border in `border` around the
/// outermost pixels the fill covers (the crisp-hairline rule -- a border drawn on the measured
/// rect's fractional edges is lit half-heartedly, and a frame bright on three sides and dim on the
/// fourth reads as a mistake). One helper, two callers: the border's colour is the only thing a
/// legend panel and a readout panel do not share.
void draw_readout_panel(const Rect& panel, Color border);
/// One trace's colour swatch in `box`, opaque whatever the trace's own alpha is: a swatch drawn at
/// a trace's 15% would be the least legible thing in the frame, and the swatch is the one element
/// that says which colour an entry or a readout row *is*. The legend's entries and the crosshair's
/// rows both read it from here.
void draw_swatch(const Plot& plot, u32 trace, const Rect& box);
/// The crosshair's **lines and markers**: the vertical and horizontal strokes and a small /// The crosshair's **lines and markers**: the vertical and horizontal strokes and a small
/// cross at each trace's intersection. /// cross at each trace's intersection.
/// ///
+42 -56
View File
@@ -1138,29 +1138,10 @@ void Renderer::draw_legend(const Plot& plot, const PlotLayout& layout) {
const LegendLayout& legend = layout.legend; const LegendLayout& legend = layout.legend;
if (legend.rows.empty()) return; if (legend.rows.empty()) return;
const Rect& panel = legend.panel; // The panel first, so every entry is on top of it. A legend's border is the frame's axis colour;
if (plot.legend().frame) { // the readout's is the crosshair's own (see draw_crosshair_readout), which is the only thing that
// The panel first, so every entry is on top of it: the frame's own background // differs between the two panels -- so it is the one parameter the shared helper takes.
// at a partial alpha, which dims whatever it covers instead of erasing it. if (plot.legend().frame) draw_readout_panel(legend.panel, style_.axis_line);
Color background = style_.background;
background.a *= kLegendPanelAlpha;
fill_pixels(panel, background);
// The border frames the pixels the fill covers, not the measured rect's own
// fractional edges: those are wherever the names happened to measure to, and a
// hairline drawn on one is lit half-heartedly -- a frame bright on three sides and
// dim on the fourth reads as a mistake. These are the outermost lit columns and
// rows the fill's span rounds out to (the same rounding the fill itself does), so
// every side of the frame is one crisp hairline.
const f32 left = std::ceil(panel.x - 0.5f) + 0.5f;
const f32 right = std::ceil(panel.right() - 0.5f) - 0.5f;
const f32 top = std::ceil(panel.y - 0.5f) + 0.5f;
const f32 bottom = std::ceil(panel.bottom() - 0.5f) - 0.5f;
lines_.add_segment({left, top}, {right, top});
lines_.add_segment({left, bottom}, {right, bottom});
lines_.add_segment({left, top}, {left, bottom});
lines_.add_segment({right, top}, {right, bottom});
flush_lines(pixel_mapping(), style_.axis_line, style_.device(1.0f));
}
for (const LegendRow& row : legend.rows) { for (const LegendRow& row : legend.rows) {
// The truncation row names no trace: it has no swatch to colour and its text is // The truncation row names no trace: it has no swatch to colour and its text is
@@ -1170,16 +1151,43 @@ void Renderer::draw_legend(const Plot& plot, const PlotLayout& layout) {
stage_text({row.name.x, row.name.y}, kLegendEllipsis); stage_text({row.name.x, row.name.y}, kLegendEllipsis);
continue; continue;
} }
// Opaque whatever the trace's own alpha is: a swatch drawn at a trace's 15% would draw_swatch(plot, row.trace, row.swatch);
// be the least legible thing in the frame, and the swatch is the one element that
// says which colour a legend entry *is*.
Color swatch = plot.trace(row.trace).color;
swatch.a = 1.0f;
fill_pixels(row.swatch, swatch);
stage_text({row.name.x, row.name.y}, plot.trace(row.trace).name.c_str()); stage_text({row.name.x, row.name.y}, plot.trace(row.trace).name.c_str());
} }
} }
void Renderer::draw_readout_panel(const Rect& panel, Color border) {
if (!(panel.w > 0.0f) || !(panel.h > 0.0f)) return;
// The frame's own background at a partial alpha, which dims whatever it covers instead of erasing
// it -- the one backplate both readout panels use.
Color background = style_.background;
background.a *= kLegendPanelAlpha;
fill_pixels(panel, background);
// The border frames the pixels the fill covers, not the measured rect's own fractional edges:
// those are wherever the content happened to measure to, and a hairline drawn on one is lit
// half-heartedly -- a frame bright on three sides and dim on the fourth reads as a mistake. These
// are the outermost lit columns and rows the fill's span rounds out to (the same rounding the fill
// itself does), so every side of the frame is one crisp hairline.
const f32 left = std::ceil(panel.x - 0.5f) + 0.5f;
const f32 right = std::ceil(panel.right() - 0.5f) - 0.5f;
const f32 top = std::ceil(panel.y - 0.5f) + 0.5f;
const f32 bottom = std::ceil(panel.bottom() - 0.5f) - 0.5f;
lines_.add_segment({left, top}, {right, top});
lines_.add_segment({left, bottom}, {right, bottom});
lines_.add_segment({left, top}, {left, bottom});
lines_.add_segment({right, top}, {right, bottom});
flush_lines(pixel_mapping(), border, style_.device(1.0f));
}
void Renderer::draw_swatch(const Plot& plot, u32 trace, const Rect& box) {
// Opaque whatever the trace's own alpha is: a swatch drawn at a trace's 15% would be the least
// legible thing in the frame, and the swatch is the one element that says which colour an entry or
// a readout row *is*. One rule, used by the legend and by the crosshair's readout alike.
Color swatch = plot.trace(trace).color;
swatch.a = 1.0f;
fill_pixels(box, swatch);
}
void Renderer::draw_crosshair_lines(const Plot& plot, const PlotLayout& layout) { void Renderer::draw_crosshair_lines(const Plot& plot, const PlotLayout& layout) {
const CrosshairLayout& crosshair = layout.crosshair; const CrosshairLayout& crosshair = layout.crosshair;
if (!crosshair.active) return; if (!crosshair.active) return;
@@ -1225,36 +1233,14 @@ void Renderer::draw_crosshair_readout(const Plot& plot, const PlotLayout& layout
const CrosshairLayout& crosshair = layout.crosshair; const CrosshairLayout& crosshair = layout.crosshair;
if (!crosshair.active) return; if (!crosshair.active) return;
// The readout: the same backplate a legend has -- the frame's background at a partial // The readout: the same backplate a legend has (the shared `draw_readout_panel()`), with its
// alpha, so the data stays legible through it -- with its border in the crosshair's own // border in the crosshair's own colour -- which is what makes the panel read as part of the
// colour, which is what makes the panel read as part of the instrument that placed it // instrument that placed it rather than as a second legend.
// rather than as a second legend. draw_readout_panel(crosshair.panel, style_.crosshair);
if (crosshair.panel.w > 0.0f && crosshair.panel.h > 0.0f) {
const Rect& panel = crosshair.panel;
Color background = style_.background;
background.a *= kLegendPanelAlpha;
fill_pixels(panel, background);
// The crisp-hairline rule of the legend's border: the outermost *lit* columns and rows of
// the fill's span, not the measured rect's fractional edges, so all four sides are one
// hairline rather than three bright ones and a dim one.
const f32 left = std::ceil(panel.x - 0.5f) + 0.5f;
const f32 right = std::ceil(panel.right() - 0.5f) - 0.5f;
const f32 top = std::ceil(panel.y - 0.5f) + 0.5f;
const f32 bottom = std::ceil(panel.bottom() - 0.5f) - 0.5f;
lines_.add_segment({left, top}, {right, top});
lines_.add_segment({left, bottom}, {right, bottom});
lines_.add_segment({left, top}, {left, bottom});
lines_.add_segment({right, top}, {right, bottom});
flush_lines(pixel_mapping(), style_.crosshair, style_.device(1.0f));
}
for (const CrosshairRow& row : crosshair.rows) { for (const CrosshairRow& row : crosshair.rows) {
if (!row.listed) continue; if (!row.listed) continue;
// The swatch is the trace's colour opaque, as a legend's is: it is the one element that draw_swatch(plot, row.trace, row.swatch);
// says which signal the row is about.
Color swatch = plot.trace(row.trace).color;
swatch.a = 1.0f;
fill_pixels(row.swatch, swatch);
stage_text({row.label_box.x, row.label_box.y}, row.label.c_str()); stage_text({row.label_box.x, row.label_box.y}, row.label.c_str());
// A row with no sample at this x has no value to write -- and it is *not* written as a // A row with no sample at this x has no value to write -- and it is *not* written as a
// zero or a dash: the box is empty and the number is simply absent (see CrosshairRow). // zero or a dash: the box is empty and the number is simply absent (see CrosshairRow).