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
+32
-20
@@ -322,7 +322,10 @@ const PlotLayout& Plot::layout() {
|
||||
// which is precisely what stops this from fighting the range the user chose.
|
||||
autoscale_axes();
|
||||
|
||||
const f32 pad = style_.grid_label_pad;
|
||||
// The label padding is a logical size, so it is converted once, here: every strip
|
||||
// and inset below reads this value in device pixels, like the backend metrics it
|
||||
// is added to.
|
||||
const f32 pad = style_.device(style_.grid_label_pad);
|
||||
// Seeds: PlotLayout's defaults when nobody can measure text, otherwise the
|
||||
// metrics the text backend does know (its line height, and just the padding
|
||||
// until the first tick list says how wide the labels are).
|
||||
@@ -398,9 +401,11 @@ const PlotLayout& Plot::layout() {
|
||||
|
||||
void Plot::refresh_ticks() {
|
||||
// Ticks are generated for the final axes size: an x axis' density depends on
|
||||
// the plot's width and a y axis' on its height.
|
||||
for (Axis& a : x_axes_) a.refresh(layout_.axes.w, style_.tick_min_px, true);
|
||||
for (Axis& a : y_axes_) a.refresh(layout_.axes.h, style_.tick_min_px, true);
|
||||
// the plot's width and a y axis' on its height. The minimum spacing is a logical
|
||||
// size, so it is converted here: a 2x display therefore gets the same *count* of
|
||||
// major ticks over the same logical width rather than twice as many.
|
||||
for (Axis& a : x_axes_) a.refresh(layout_.axes.w, style_.device(style_.tick_min_px), true);
|
||||
for (Axis& a : y_axes_) a.refresh(layout_.axes.h, style_.device(style_.tick_min_px), true);
|
||||
}
|
||||
|
||||
void Plot::build_grid() {
|
||||
@@ -451,7 +456,7 @@ f32 Plot::y_title_strip_width() const {
|
||||
if (text_ == nullptr) return 0.0f;
|
||||
const u32 titles = titled_y_axis_count();
|
||||
if (titles == 0u) return 0.0f;
|
||||
return static_cast<f32>(titles) * text_->height() + 2.0f * style_.grid_label_pad;
|
||||
return static_cast<f32>(titles) * text_->height() + 2.0f * style_.device(style_.grid_label_pad);
|
||||
}
|
||||
|
||||
f32 Plot::y_label_width() const {
|
||||
@@ -463,7 +468,7 @@ f32 Plot::y_label_width() const {
|
||||
widest = std::max(widest, text_->measure(tick.label.c_str()));
|
||||
}
|
||||
}
|
||||
return widest + 2.0f * style_.grid_label_pad;
|
||||
return widest + 2.0f * style_.device(style_.grid_label_pad);
|
||||
}
|
||||
|
||||
void Plot::build_legend() {
|
||||
@@ -474,9 +479,17 @@ void Plot::build_legend() {
|
||||
// plot that lays out exactly as it did before legends existed.
|
||||
if (text_ == nullptr || !legend_.visible) return;
|
||||
|
||||
const f32 pad = legend_.pad;
|
||||
// Every one of these is a logical size -- the legend is chrome a user reads --
|
||||
// while `text_->height()` and the name measures are device: converting each one
|
||||
// once, into a local, is what keeps the panel's arithmetic in device pixels
|
||||
// throughout, like the strips it is fitted into.
|
||||
const f32 pad = style_.device(legend_.pad);
|
||||
const f32 margin = style_.device(legend_.margin);
|
||||
const f32 swatch_w = style_.device(legend_.swatch_w);
|
||||
const f32 swatch_h = style_.device(legend_.swatch_h);
|
||||
const f32 row_gap = style_.device(legend_.row_gap);
|
||||
const f32 line_h = text_->height();
|
||||
const f32 row_h = std::max(legend_.swatch_h, line_h);
|
||||
const f32 row_h = std::max(swatch_h, line_h);
|
||||
// Entry widths, prefix-maxed: dropping from the end then costs no re-measure, and
|
||||
// the panel's width after a drop is the width of an entry that is still listed.
|
||||
legend_name_px_.clear();
|
||||
@@ -492,8 +505,8 @@ void Plot::build_legend() {
|
||||
if (entries == 0u) return;
|
||||
|
||||
const f32 ellipsis_w = text_->measure(kLegendEllipsis);
|
||||
const f32 room_w = layout_.axes.w - 2.0f * legend_.margin;
|
||||
const f32 room_h = layout_.axes.h - 2.0f * legend_.margin;
|
||||
const f32 room_w = layout_.axes.w - 2.0f * margin;
|
||||
const f32 room_h = layout_.axes.h - 2.0f * margin;
|
||||
|
||||
// Fit, from every entry down: the panel is a row per entry with `row_gap` between,
|
||||
// and, once an entry has been dropped, one more row for the marker that says so.
|
||||
@@ -510,8 +523,8 @@ void Plot::build_legend() {
|
||||
const f32 widest = truncated ? std::max(legend_name_px_[keep - 1u], ellipsis_w)
|
||||
: legend_name_px_[keep - 1u];
|
||||
const f32 rows = static_cast<f32>(keep + (truncated ? 1u : 0u));
|
||||
panel_w = 2.0f * pad + legend_.swatch_w + pad + widest;
|
||||
panel_h = 2.0f * pad + rows * row_h + (rows - 1.0f) * legend_.row_gap;
|
||||
panel_w = 2.0f * pad + swatch_w + pad + widest;
|
||||
panel_h = 2.0f * pad + rows * row_h + (rows - 1.0f) * row_gap;
|
||||
if (panel_w <= room_w && panel_h <= room_h) break;
|
||||
if (--keep == 0u) return; // not even one entry and its marker fit
|
||||
}
|
||||
@@ -519,13 +532,13 @@ void Plot::build_legend() {
|
||||
// Anchored in the corner `place` names and held `margin` off both edges, which is
|
||||
// the inset the fit above was measured against -- so the panel is inside `axes` by
|
||||
// construction and can never cross it, whatever corner it was put in.
|
||||
f32 x = layout_.axes.x + legend_.margin;
|
||||
f32 y = layout_.axes.y + legend_.margin;
|
||||
f32 x = layout_.axes.x + margin;
|
||||
f32 y = layout_.axes.y + margin;
|
||||
if (legend_.place == LegendPlace::TopRight || legend_.place == LegendPlace::BottomRight) {
|
||||
x = layout_.axes.right() - legend_.margin - panel_w;
|
||||
x = layout_.axes.right() - margin - panel_w;
|
||||
}
|
||||
if (legend_.place == LegendPlace::BottomLeft || legend_.place == LegendPlace::BottomRight) {
|
||||
y = layout_.axes.bottom() - legend_.margin - panel_h;
|
||||
y = layout_.axes.bottom() - margin - panel_h;
|
||||
}
|
||||
legend.panel = Rect{x, y, panel_w, panel_h};
|
||||
|
||||
@@ -534,19 +547,18 @@ void Plot::build_legend() {
|
||||
// The name box starts one swatch and one gutter in -- exactly what the panel's
|
||||
// width allowed for -- and both boxes are centred in the row's height, so a swatch
|
||||
// shorter than the text line is centred on it instead of sitting on its baseline.
|
||||
const f32 name_x = x + pad + legend_.swatch_w + pad;
|
||||
const f32 name_x = x + pad + swatch_w + pad;
|
||||
f32 row_y = y + pad;
|
||||
for (u32 i = 0; i < traces_.size() && legend.rows.size() < keep; ++i) {
|
||||
const Trace& trace = traces_[i];
|
||||
if (!trace.visible() || trace.name.empty()) continue;
|
||||
LegendRow row;
|
||||
row.trace = i;
|
||||
row.swatch = Rect{x + pad, row_y + 0.5f * (row_h - legend_.swatch_h), legend_.swatch_w,
|
||||
legend_.swatch_h};
|
||||
row.swatch = Rect{x + pad, row_y + 0.5f * (row_h - swatch_h), swatch_w, swatch_h};
|
||||
row.name = Rect{name_x, row_y + 0.5f * (row_h - line_h),
|
||||
text_->measure(trace.name.c_str()), line_h};
|
||||
legend.rows.push_back(row);
|
||||
row_y += row_h + legend_.row_gap;
|
||||
row_y += row_h + row_gap;
|
||||
}
|
||||
if (keep < entries) {
|
||||
// Entries were dropped; the marker takes the row after the last kept one, with no
|
||||
|
||||
Reference in new issue
Block a user