diff --git a/include/plot/render/renderer.hpp b/include/plot/render/renderer.hpp index fc8557a..313e623 100644 --- a/include/plot/render/renderer.hpp +++ b/include/plot/render/renderer.hpp @@ -487,6 +487,18 @@ class Renderer { /// them again, so the panel a test asserts and the panel this draws are the same /// numbers. 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 /// cross at each trace's intersection. /// diff --git a/src/render/renderer.cpp b/src/render/renderer.cpp index 4bb0fb4..c76edb7 100644 --- a/src/render/renderer.cpp +++ b/src/render/renderer.cpp @@ -1138,29 +1138,10 @@ void Renderer::draw_legend(const Plot& plot, const PlotLayout& layout) { const LegendLayout& legend = layout.legend; if (legend.rows.empty()) return; - const Rect& panel = legend.panel; - if (plot.legend().frame) { - // The panel first, so every entry is on top of it: the frame's own background - // at a partial alpha, which dims whatever it covers instead of erasing it. - 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)); - } + // The panel first, so every entry is on top of it. A legend's border is the frame's axis colour; + // the readout's is the crosshair's own (see draw_crosshair_readout), which is the only thing that + // differs between the two panels -- so it is the one parameter the shared helper takes. + if (plot.legend().frame) draw_readout_panel(legend.panel, style_.axis_line); for (const LegendRow& row : legend.rows) { // 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); continue; } - // 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 a legend entry *is*. - Color swatch = plot.trace(row.trace).color; - swatch.a = 1.0f; - fill_pixels(row.swatch, swatch); + draw_swatch(plot, row.trace, row.swatch); 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) { const CrosshairLayout& crosshair = layout.crosshair; if (!crosshair.active) return; @@ -1225,36 +1233,14 @@ void Renderer::draw_crosshair_readout(const Plot& plot, const PlotLayout& layout const CrosshairLayout& crosshair = layout.crosshair; if (!crosshair.active) return; - // The readout: the same backplate a legend has -- the frame's background at a partial - // alpha, so the data stays legible through it -- with its border in the crosshair's own - // colour, which is what makes the panel read as part of the instrument that placed it - // rather than as a second legend. - 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)); - } + // The readout: the same backplate a legend has (the shared `draw_readout_panel()`), with its + // border in the crosshair's own colour -- which is what makes the panel read as part of the + // instrument that placed it rather than as a second legend. + draw_readout_panel(crosshair.panel, style_.crosshair); for (const CrosshairRow& row : crosshair.rows) { if (!row.listed) continue; - // The swatch is the trace's colour opaque, as a legend's is: it is the one element that - // says which signal the row is about. - Color swatch = plot.trace(row.trace).color; - swatch.a = 1.0f; - fill_pixels(row.swatch, swatch); + draw_swatch(plot, row.trace, row.swatch); 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 // zero or a dash: the box is empty and the number is simply absent (see CrosshairRow).