mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
136 lines
3.8 KiB
JavaScript
136 lines
3.8 KiB
JavaScript
/** Map an integer tuner slot directly to one available directory station. */
|
|
export function radioTunerSlot(value, stationCount) {
|
|
const count = Math.max(0, Math.floor(Number(stationCount) || 0));
|
|
if (!count)
|
|
return {
|
|
slot: 0,
|
|
max: 0,
|
|
locked: false,
|
|
stationIndex: -1,
|
|
leftIndex: -1,
|
|
rightIndex: -1,
|
|
};
|
|
const max = Math.max(0, count - 1);
|
|
const slot = Math.min(max, Math.max(0, Math.round(Number(value) || 0)));
|
|
return {
|
|
slot,
|
|
max,
|
|
locked: true,
|
|
stationIndex: slot,
|
|
leftIndex: slot,
|
|
rightIndex: slot,
|
|
};
|
|
}
|
|
|
|
/** Snap a tuner release to the nearest available directory station. */
|
|
export function radioTunerCommitSlot(value, stationCount) {
|
|
return radioTunerSlot(value, stationCount);
|
|
}
|
|
|
|
/** Map one pointer coordinate to continuous absolute directory progress. */
|
|
export function radioTunerPointerPosition(
|
|
clientX,
|
|
left,
|
|
width,
|
|
stationCount,
|
|
insetPx = 7,
|
|
) {
|
|
const count = Math.max(0, Math.floor(Number(stationCount) || 0));
|
|
if (!count) return { ratio: 0, coordinate: 0, stationIndex: -1 };
|
|
if (count === 1) return { ratio: 0.5, coordinate: 0, stationIndex: 0 };
|
|
const inset = Math.max(0, Number(insetPx) || 0);
|
|
const usableWidth = Math.max(1, (Number(width) || 0) - inset * 2);
|
|
const ratio = Math.min(
|
|
1,
|
|
Math.max(
|
|
0,
|
|
((Number(clientX) || 0) - (Number(left) || 0) - inset) / usableWidth,
|
|
),
|
|
);
|
|
const coordinate = ratio * (count - 1);
|
|
return {
|
|
ratio,
|
|
coordinate,
|
|
stationIndex: Math.min(
|
|
count - 1,
|
|
Math.max(0, Math.floor(coordinate + 0.5)),
|
|
),
|
|
};
|
|
}
|
|
|
|
/** Build a bounded virtual tuner tape around one continuous directory coordinate. */
|
|
export function buildRadioTunerTicks(
|
|
coordinate,
|
|
stationCount,
|
|
width,
|
|
{
|
|
insetPx = 7,
|
|
minPitchPx = 14,
|
|
speedFactor = 5,
|
|
overscan = 2,
|
|
labelStep = 6,
|
|
} = {},
|
|
) {
|
|
const count = Math.max(0, Math.floor(Number(stationCount) || 0));
|
|
const dialWidth = Math.max(0, Number(width) || 0);
|
|
const inset = Math.max(0, Number(insetPx) || 0);
|
|
const usableWidth = Math.max(0, dialWidth - inset * 2);
|
|
if (!count) return { ticks: [], needleX: inset, pitchPx: 0, ratio: 0 };
|
|
const value = Math.min(count - 1, Math.max(0, Number(coordinate) || 0));
|
|
if (count === 1) {
|
|
return {
|
|
ticks: [
|
|
{
|
|
stationIndex: 0,
|
|
channel: 1,
|
|
xPx: inset + usableWidth / 2,
|
|
current: true,
|
|
label: '01',
|
|
},
|
|
],
|
|
needleX: inset + usableWidth / 2,
|
|
pitchPx: Math.max(1, Number(minPitchPx) || 14),
|
|
ratio: 0.5,
|
|
};
|
|
}
|
|
const directoryStep = usableWidth / (count - 1);
|
|
const pitchPx = Math.max(
|
|
Math.max(1, Number(minPitchPx) || 14),
|
|
directoryStep * Math.max(1, Number(speedFactor) || 5),
|
|
);
|
|
const needleX = inset + directoryStep * value;
|
|
const overscanPx = pitchPx * Math.max(0, Number(overscan) || 0);
|
|
const first = Math.max(
|
|
0,
|
|
Math.ceil(value + (-overscanPx - needleX) / pitchPx),
|
|
);
|
|
const last = Math.min(
|
|
count - 1,
|
|
Math.floor(value + (dialWidth + overscanPx - needleX) / pitchPx),
|
|
);
|
|
const currentIndex = Math.min(
|
|
count - 1,
|
|
Math.max(0, Math.floor(value + 0.5)),
|
|
);
|
|
const majorEvery = Math.max(1, Math.floor(Number(labelStep) || 6));
|
|
const labelWidth = Math.max(2, String(count).length);
|
|
const ticks = [];
|
|
for (let stationIndex = first; stationIndex <= last; stationIndex += 1) {
|
|
const channel = stationIndex + 1;
|
|
const current = stationIndex === currentIndex;
|
|
const labelled =
|
|
current ||
|
|
stationIndex === 0 ||
|
|
stationIndex === count - 1 ||
|
|
channel % majorEvery === 0;
|
|
ticks.push({
|
|
stationIndex,
|
|
channel,
|
|
xPx: needleX + pitchPx * (stationIndex - value),
|
|
current,
|
|
label: labelled ? String(channel).padStart(labelWidth, '0') : '',
|
|
});
|
|
}
|
|
return { ticks, needleX, pitchPx, ratio: value / (count - 1) };
|
|
}
|