47f2a063e7
Aligns inKnobFace's hit radius with computeKnob's draw-side min(w,h) rule and adds a non-square-rect test; clamps halfSpan for degenerate waveform bands with a test; fixes stale docs/comments and annotates an uncommitted perf measurement.
134 lines
4.6 KiB
C++
134 lines
4.6 KiB
C++
// component_geometry — pure implementation. See component_geometry.h.
|
|
|
|
#include "core/ui/component_geometry.h"
|
|
|
|
namespace reasampler::ui {
|
|
|
|
bool hitTestBox(int px, int py, const KitBox& box) {
|
|
if (box.empty()) return false;
|
|
return px >= box.x && px < box.x + box.width &&
|
|
py >= box.y && py < box.y + box.height;
|
|
}
|
|
|
|
KitButtonBox computeButtonBox(const KitBox& cell, int padding) {
|
|
if (cell.empty()) return {};
|
|
if (padding < 0) padding = 0;
|
|
KitBox b;
|
|
b.x = cell.x + padding;
|
|
b.y = cell.y + padding;
|
|
b.width = cell.width - 2 * padding;
|
|
b.height = cell.height - 2 * padding;
|
|
if (b.empty()) return {};
|
|
return KitButtonBox{b};
|
|
}
|
|
|
|
SliderGeometry computeSlider(const KitBox& control, double value,
|
|
int handleSize, int trackThickness) {
|
|
if (control.empty() || handleSize <= 0 || trackThickness <= 0) return {};
|
|
if (control.width < handleSize || control.height < handleSize) return {};
|
|
|
|
if (value < 0.0) value = 0.0;
|
|
if (value > 1.0) value = 1.0;
|
|
|
|
const int half = handleSize / 2;
|
|
|
|
KitBox track;
|
|
track.x = control.x + half;
|
|
track.width = control.width - handleSize; // travel span for the handle centre
|
|
if (track.width < 0) track.width = 0;
|
|
track.height = trackThickness;
|
|
track.y = control.y + (control.height - trackThickness) / 2;
|
|
|
|
const int centre = track.x + static_cast<int>(value * track.width + 0.5);
|
|
KitBox handle;
|
|
handle.x = centre - half;
|
|
handle.y = control.y + (control.height - handleSize) / 2;
|
|
handle.width = handleSize;
|
|
handle.height = handleSize;
|
|
|
|
KitBox filled;
|
|
filled.x = track.x;
|
|
filled.y = track.y;
|
|
filled.width = centre - track.x;
|
|
if (filled.width < 0) filled.width = 0;
|
|
filled.height = track.height;
|
|
|
|
return SliderGeometry{track, filled, handle};
|
|
}
|
|
|
|
double sliderValueAt(int px, const KitBox& control, int handleSize) {
|
|
if (control.empty() || handleSize <= 0) return 0.0;
|
|
if (control.width < handleSize) return 0.0;
|
|
|
|
const int half = handleSize / 2;
|
|
const int trackStart = control.x + half;
|
|
const int trackSpan = control.width - handleSize; // matches computeSlider's travel
|
|
if (trackSpan <= 0) return 0.0;
|
|
|
|
if (px <= trackStart) return 0.0;
|
|
if (px >= trackStart + trackSpan) return 1.0;
|
|
return static_cast<double>(px - trackStart) / static_cast<double>(trackSpan);
|
|
}
|
|
|
|
ListRowBox computeListRow(const KitBox& list, int index, int rowHeight) {
|
|
if (list.empty() || rowHeight <= 0 || index < 0) return {};
|
|
const int top = list.y + index * rowHeight;
|
|
if (top >= list.y + list.height) return {};
|
|
KitBox b;
|
|
b.x = list.x;
|
|
b.y = top;
|
|
b.width = list.width;
|
|
b.height = rowHeight;
|
|
return ListRowBox{index, b};
|
|
}
|
|
|
|
int hitTestListRow(int px, int py, const KitBox& list, int rowHeight, int rowCount) {
|
|
if (list.empty() || rowHeight <= 0 || rowCount <= 0) return -1;
|
|
if (px < list.x || px >= list.x + list.width ||
|
|
py < list.y || py >= list.y + list.height)
|
|
return -1;
|
|
const int row = (py - list.y) / rowHeight;
|
|
if (row < 0 || row >= rowCount) return -1;
|
|
return row;
|
|
}
|
|
|
|
int waveformColumnCount(const KitBox& box) {
|
|
const int w = box.width - 4; // fixed 2px inset each side (matches drawWaveform)
|
|
return w > 0 ? w : 0;
|
|
}
|
|
|
|
WaveformBand waveformBand(int bandTop, int bandHeight) {
|
|
WaveformBand b;
|
|
b.top = bandTop;
|
|
b.height = bandHeight;
|
|
b.midY = bandTop + bandHeight / 2;
|
|
// matches drawWaveform's 2px vertical breathing room; clamped at 0 so a degenerate band
|
|
// (height <= 3, where this would otherwise go negative) can't flip a positive peak below
|
|
// the zero line.
|
|
const int rawHalfSpan = (bandHeight / 2) - 2;
|
|
b.halfSpan = rawHalfSpan > 0 ? rawHalfSpan : 0;
|
|
// lo/hi (waveformColumnSpan) are midY-height/2 .. midY+height/2-1 — asymmetric by one px for
|
|
// even heights. Unreachable while |compressAmplitudeForDisplay| <= 1, so left as-is.
|
|
return b;
|
|
}
|
|
|
|
WaveformColumnSpan waveformColumnSpan(const WaveformBand& band,
|
|
double compressedMax, double compressedMin) {
|
|
const int lo = band.top;
|
|
const int hi = band.top + band.height - 1;
|
|
|
|
WaveformColumnSpan s;
|
|
s.top = band.midY - static_cast<int>(compressedMax * band.halfSpan);
|
|
s.bottom = band.midY - static_cast<int>(compressedMin * band.halfSpan);
|
|
s.topF = band.midY - compressedMax * band.halfSpan;
|
|
s.bottomF = band.midY - compressedMin * band.halfSpan;
|
|
|
|
if (s.top < lo) s.top = lo;
|
|
if (s.bottom > hi) s.bottom = hi;
|
|
if (s.topF < lo) s.topF = lo;
|
|
if (s.bottomF > hi) s.bottomF = hi;
|
|
return s;
|
|
}
|
|
|
|
} // namespace reasampler::ui
|