feat: an open box zoom draws the frame the release will apply

The controller has always tracked its selection; nothing drew it, so a Ctrl or
Shift drag framed a range with no feedback until the release.

The preview is the area the release will change, not what the pointer swept:
Ctrl frames the x range only, so a band across the plot is what it will zoom,
where the sliver a straight horizontal drag sweeps is only the gesture. The
plot carries the rect a host hands it -- it learns nothing about pointers --
and the renderer draws a low-alpha fill under a light border over everything
else inside the axes.
This commit is contained in:
Martino Ferrari committed 2026-10-11 11:07:55 +02:00
1 parent 1fc97d818f
commit be1f31a04f
14 files changed
+276 -1

No files matched your search

+24
View File
@@ -545,6 +545,9 @@ void Renderer::draw_plot(Plot& plot, const PlotLayout& layout) {
// panel is drawn over the legend and every other overlay rather than under them, and its
// text joins the frame's over flush with the tick labels and titles.
draw_crosshair_readout(plot, layout);
// The selection frame last of all: it is the gesture still in progress, and nothing inside the axes
// should be able to sit on top of the area being chosen.
draw_selection_frame(layout);
clear_clip();
draw_tick_labels(plot, layout);
draw_titles(plot, layout);
@@ -1960,6 +1963,27 @@ void Renderer::stage_marker(plot::Marker marker, Vec2 centre_px, f32 size_px) {
}
}
void Renderer::draw_selection_frame(const PlotLayout& layout) {
const Rect& frame = layout.selection;
// Two guards, not one: a drag swept flat in one direction has a real extent on one axis and none on
// the other, and a border on that is still the gesture's own answer -- the fill is what a zero
// extent has nothing of.
if (!(frame.w > 0.0f) && !(frame.h > 0.0f)) return;
if (frame.w > 0.0f && frame.h > 0.0f) fill_pixels(frame, style_.selection_fill);
// The border frames the pixels the fill covers and not the measured rect's own fractional edges:
// the readout panel's rounding and the readout panel's reason, since a hairline straddling a
// fractional edge is a frame bright on two sides and dim on the others.
const f32 left = std::ceil(frame.x - 0.5f) + 0.5f;
const f32 right = std::ceil(frame.right() - 0.5f) - 0.5f;
const f32 top = std::ceil(frame.y - 0.5f) + 0.5f;
const f32 bottom = std::ceil(frame.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_.selection_edge, style_.device(1.0f));
}
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