feat(render): logical style metrics with a UI scale, so chrome has a consistent physical size
This commit is contained in:
1 parent
1f03631e98
commit
7500fc90d2
21 files changed
+708
-82
No files matched your search
@@ -20,10 +20,15 @@ constexpr f64 kKeyZoomNotches = 1.0;
|
||||
/// pixels, a different unit from a wheel notch, so it must not reuse
|
||||
/// kZoomPerNotch: 200 px of drag is one decade, exp(-200 * ln10/200) == 0.1.
|
||||
constexpr f64 kZoomPerPixel = std::numbers::ln10 / 200.0;
|
||||
/// How close (in pixels) the pointer must be to a cursor to grab it.
|
||||
constexpr f32 kCursorHitRadiusPx = 5.0f;
|
||||
/// A box-zoom selection narrower than this on either side is a click.
|
||||
constexpr f32 kMinBoxPixels = 4.0f;
|
||||
/// How close the pointer must be to a cursor to grab it, in **logical** pixels: the
|
||||
/// listener's aim is a physical distance, so the device-pixel radius it is compared
|
||||
/// against is this times `Plot::ui_scale()` (see `grab_radius()`). An unscaled
|
||||
/// constant would make grabbing physically harder -- half as forgiving -- on a 2x
|
||||
/// display, which is the defect the scale exists to prevent.
|
||||
constexpr f32 kCursorHitRadiusLogical = 5.0f;
|
||||
/// A box-zoom selection narrower than this on either side is a click. Logical for
|
||||
/// the same reason: the gesture is the same hand movement on any display.
|
||||
constexpr f32 kMinBoxLogical = 4.0f;
|
||||
|
||||
/// An axis endpoint in transform space: log10 when the axis is logarithmic, the
|
||||
/// value itself otherwise. Pan and zoom arithmetic runs in this space so a log
|
||||
@@ -39,6 +44,15 @@ f64 from_space(f64 v, bool log) noexcept {
|
||||
/// key) zoom in, so the span shrinks by `exp(-notches * kZoomPerNotch)`.
|
||||
f64 zoom_factor(f64 notches) noexcept { return std::exp(-notches * kZoomPerNotch); }
|
||||
|
||||
/// Device pixels per logical unit for the tolerances below. Read from the plot the
|
||||
/// controller already holds -- every forwarded coordinate is in device pixels, and
|
||||
/// the plot is the one that knows what a device pixel is worth -- rather than kept
|
||||
/// as a copy that a host would have to update in step with the renderer's.
|
||||
f32 tolerance_scale(const Plot& plot) noexcept {
|
||||
const f32 scale = plot.ui_scale();
|
||||
return scale > 0.0f ? scale : 1.0f;
|
||||
}
|
||||
|
||||
/// True when a coordinate is one arithmetic can work with. Every event here comes
|
||||
/// from a host, and a host can hand over anything: a toolkit's "no pointer"
|
||||
/// sentinel, a NaN from a degenerate mapping of its own, an infinity from a lost
|
||||
@@ -267,8 +281,11 @@ void Controller::box_zoom_update(const PointerEvent& e) {
|
||||
|
||||
void Controller::apply_box_zoom() {
|
||||
const Rect sel = selection_;
|
||||
// A selection under a few pixels on either side is a click: no zoom.
|
||||
if (sel.w < kMinBoxPixels || sel.h < kMinBoxPixels) return;
|
||||
// A selection under a few *logical* pixels on either side is a click: no zoom.
|
||||
// The selection is measured in device pixels like everything the controller is
|
||||
// handed, so the threshold is the logical one at this plot's scale.
|
||||
const f32 min_box = kMinBoxLogical * tolerance_scale(*plot_);
|
||||
if (sel.w < min_box || sel.h < min_box) return;
|
||||
|
||||
// Clamped to the axes rect the transforms are built from, so a drag that left
|
||||
// the plot area cannot ask for data outside what the box covers on screen.
|
||||
@@ -324,6 +341,7 @@ u64 Controller::cursor_at(f32 px, f32 py) const {
|
||||
const PlotLayout& lay = std::as_const(*plot_).layout();
|
||||
if (!lay.axes.contains(px, py)) return kInvalid;
|
||||
|
||||
const f32 radius = kCursorHitRadiusLogical * tolerance_scale(*plot_);
|
||||
u64 best = kInvalid;
|
||||
f32 best_d = 0.0f;
|
||||
for (u32 i = 0; i < plot_->cursor_count(); ++i) {
|
||||
@@ -331,7 +349,7 @@ u64 Controller::cursor_at(f32 px, f32 py) const {
|
||||
if (!cursor.visible) continue;
|
||||
const f32 sx = static_cast<f32>(plot_->x_transform(cursor.axis_x).to_screen_x(cursor.x));
|
||||
const f32 d = std::fabs(sx - px);
|
||||
if (d > kCursorHitRadiusPx) continue;
|
||||
if (d > radius) continue;
|
||||
if (best == kInvalid || d < best_d) {
|
||||
best = i;
|
||||
best_d = d;
|
||||
|
||||
Reference in new issue
Block a user