Skip to content

Add OSD preview rulers #4567

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -7721,5 +7721,9 @@
"osdDescElementLidar": {
"message": "$t(osdTextElementLidar.message)",
"description": "Don't translate!!!"
},
"osdSetupPreviewCheckRulers": {
"message": "Rulers",
"description": "Label for the checkbox to toggle OSD preview rulers around the preview"
}
}
27 changes: 27 additions & 0 deletions src/css/tabs/osd.less
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
border-radius: 5px;
border: 1px solid var(--surface-500);
-webkit-appearance: none;
appearance: none;
&::-webkit-progress-bar {
background-color: var(--text);
border-radius: 4px;
Expand Down Expand Up @@ -159,6 +160,7 @@
}
}
.display-layout {
height: 100%;
label {
margin: 0.25em 0.1em;
display: inline-block;
Expand Down Expand Up @@ -187,10 +189,27 @@
/* please don't copy the generic background image from another project
* and replace the one that @nathantsoi took :)
*/
.preview-container {
position: relative;
display: block;
width: 100%;
height: 100%;
}

.ruler-overlay {
position: absolute;
top: 0;
left: 0;
z-index: 10;
pointer-events: none;
}

.preview {
background: url(../../images/osd-bg-1.jpg);
background-size: cover;
background-repeat: no-repeat;
margin-top: 20px;
margin-left: 20px;
}
.gui_box_titlebar {
label {
Expand All @@ -204,6 +223,7 @@
}
.gui_box_bottombar {
text-align: center;
margin-top: 30px;
}
.row {
display: flex;
Expand Down Expand Up @@ -257,6 +277,13 @@
margin-left: 5px;
vertical-align: text-bottom;
}
.osd-preview-rulers-group {
margin-left: 10px;
}
.osd-preview-rulers-selector {
margin-left: 5px;
vertical-align: text-bottom;
}
.char.mouseover {
background: rgba(255, 255, 255, 0.4);
}
Expand Down
263 changes: 259 additions & 4 deletions src/js/tabs/osd.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ const positionConfigs = {
x: 1,
y: 1,
}),
grow: {
x: 0,
y: 1,
},
gridPos: [0, 0],
},
TC: {
Expand Down Expand Up @@ -2328,6 +2324,240 @@ OSD.updateDisplaySize = function () {
};
};

// Ruler config object for all magic numbers
OSD.rulerConfig = {
meterThickness: 16, // px
tickMinor: 4, // px
tickMajor: 8, // px
vertTickMajor: 12, // px
labelPadding: 12, // px
topLabelOffset: 4, // px
sideLabelOffset: 12, // px
sideLabelOffsetMajor: 16, // px
bottomLabelOffset: 12, // px
edgeGap: 12, // px
minEdgePadding: 12, // px
verticalLabelStep: 2,
bumpTop: 3, // px
bumpRight: 3, // px
colorMinor: "#888888",
colorMajor: "#cccccc",
colorCenter: "#ffff00",
font: "10px monospace",
};

OSD.initializeRulers = function () {
const canvas = document.querySelector(".ruler-overlay");
const preview = document.querySelector(".preview");
const container = document.querySelector(".preview-container");
const enabled = document.querySelector("#osd-preview-rulers-selector")?.checked;
if (!canvas || !preview || !container || !OSD.data?.displaySize) {
return false;
}
if (!enabled) {
canvas.style.display = "none";
preview.style.marginRight = "";
preview.style.marginLeft = "";
preview.style.marginTop = "";
container.style.paddingRight = "";
container.style.paddingLeft = "";
container.style.paddingTop = "";
return false;
}
canvas.style.display = "block";
container.style.paddingTop = "26px";
preview.style.marginRight = "30px";
preview.style.marginLeft = "30px";
return { canvas, preview, container };
};

OSD.setupRulerContext = function (canvas, preview, container, config) {
const cw = Math.max(1, Math.floor(container.clientWidth));
const ch = Math.max(1, Math.floor(container.clientHeight));
if (canvas.width !== cw) {
canvas.width = cw;
}
if (canvas.height !== ch) {
canvas.height = ch;
}
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = config.font;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const rows = preview.querySelectorAll(".row");
if (!rows.length) {
return false;
}
const colsInRow = rows[0].querySelectorAll(".char");
if (!colsInRow.length) {
return false;
}
const containerRect = container.getBoundingClientRect();
const previewRect = preview.getBoundingClientRect();
const left = Math.floor(previewRect.left - containerRect.left);
const top = Math.floor(previewRect.top - containerRect.top);
const right = Math.ceil(previewRect.right - containerRect.left);
const bottom = Math.ceil(previewRect.bottom - containerRect.top);
const charRect = colsInRow[0].getBoundingClientRect();
const cellW = charRect.width;
const cellH = charRect.height;
const cols = colsInRow.length;
const rowsCount = rows.length;
const cx = Math.floor(cols / 2);
const cy = Math.floor(rowsCount / 2);
const signPad = Math.ceil(ctx.measureText("-").width);
return {
ctx,
cw,
ch,
containerRect,
previewRect,
cols,
rowsCount,
cx,
cy,
cellW,
cellH,
left,
top,
right,
bottom,
rows,
colsInRow,
signPad,
};
};

OSD._colCenterX = function (i, containerRect, colsInRow) {
const rect = colsInRow[i].getBoundingClientRect();
return Math.round(rect.left - containerRect.left + rect.width / 2);
};
OSD._rowCenterY = function (i, containerRect, rows) {
const rect = rows[i].getBoundingClientRect();
return Math.round(rect.top - containerRect.top + rect.height / 2);
};

// Shared axis drawing helper for top/bottom horizontal rulers
function drawHorizontalAxis(ctx, params, axis) {
const { cols, containerRect, colsInRow, config } = params;
let centerIndex = Math.floor(cols / 2);
let minOffset = -centerIndex;
let maxOffset = centerIndex;
for (let i = 0; i < cols; i++) {
let offset = i - centerIndex;
const x = OSD._colCenterX(i, containerRect, colsInRow);
const isCenter = offset === 0;
const isMajor = offset % 5 === 0 || isCenter;
const majorColor = isMajor ? config.colorMajor : config.colorMinor;
const tick = isMajor ? config.tickMajor : config.tickMinor;
ctx.strokeStyle = isCenter ? config.colorCenter : majorColor;
ctx.lineWidth = 1;
ctx.beginPath();
let y0, y1, labelY;
if (axis === "top") {
y0 = Math.max(0, params.top - config.edgeGap);
y1 = Math.max(0, y0 - tick);
labelY = Math.max(config.minEdgePadding, y1 - config.topLabelOffset);
} else {
y0 = Math.min(params.ch, params.bottom + 1);
y1 = Math.min(params.ch, params.bottom + tick);
const maxLabelY = params.ch - 12;
labelY = Math.min(maxLabelY, y1 + config.bottomLabelOffset);
}
ctx.moveTo(x + 0.5, y0 + 0.5);
ctx.lineTo(x + 0.5, y1 + 0.5);
ctx.stroke();
if (isMajor && offset >= minOffset && offset <= maxOffset) {
ctx.fillStyle = isCenter ? config.colorCenter : "#ffffff";
ctx.save();
ctx.textBaseline = axis === "top" ? "bottom" : "top";
ctx.lineWidth = 3;
ctx.strokeStyle = "rgba(0,0,0,0.6)";
ctx.strokeText(offset.toString(), x, labelY);
ctx.fillText(offset.toString(), x, labelY);
ctx.restore();
}
}
}

// Shared vertical axis drawing helper for left/right rulers
function drawVerticalAxis(ctx, params, axis) {
const { rowsCount, cy, left, right, ch, cw, signPad, rows, containerRect, config } = params;
ctx.textAlign = axis === "left" ? "right" : "left";
for (let i = 0; i < rowsCount; i++) {
const y = OSD._rowCenterY(i, containerRect, rows);
const offset = i - cy;
const isCenter = i === cy;
const isMajor = Math.abs(offset) % config.verticalLabelStep === 0 || i === 0 || i === rowsCount - 1 || isCenter;
const majorColor = isMajor ? config.colorMajor : config.colorMinor;
const tick = isMajor ? config.vertTickMajor : config.tickMinor;
ctx.strokeStyle = isCenter ? config.colorCenter : majorColor;
ctx.lineWidth = 1;
ctx.beginPath();
let x0, x1, labelX;
if (axis === "left") {
x0 = left - 1;
x1 = Math.max(0, left - tick);
} else {
x0 = Math.min(cw - 1, right + 1);
x1 = Math.min(cw - 1, right + tick);
}
ctx.moveTo(x0 + 0.5, y + 0.5);
ctx.lineTo(x1 + 0.5, y + 0.5);
ctx.stroke();
if (isMajor) {
ctx.fillStyle = isCenter ? config.colorCenter : "#ffffff";
const text = offset.toString();
const textWidth = ctx.measureText(text).width;
const extra = text.startsWith("-") ? signPad : 0;
if (axis === "left") {
const desired = x1 - (isMajor ? config.sideLabelOffsetMajor : config.sideLabelOffset) - extra;
labelX = Math.max(config.minEdgePadding + textWidth, desired);
} else {
const desired = x1 + (isMajor ? config.sideLabelOffsetMajor : config.sideLabelOffset) + extra;
labelX = Math.min(cw - config.minEdgePadding - textWidth, desired);
}
ctx.lineWidth = 3;
ctx.strokeStyle = "rgba(0,0,0,0.6)";
const yLabel = Math.max(config.minEdgePadding, Math.min(ch - config.minEdgePadding, y + 0.5));
ctx.strokeText(text, labelX, yLabel);
ctx.fillText(text, labelX, yLabel);
}
}
}

OSD._drawTopAxis = function (ctx, params) {
drawHorizontalAxis(ctx, params, "top");
};
OSD._drawBottomAxis = function (ctx, params) {
drawHorizontalAxis(ctx, params, "bottom");
};
OSD._drawLeftAxis = function (ctx, params) {
drawVerticalAxis(ctx, params, "left");
};
OSD._drawRightAxis = function (ctx, params) {
drawVerticalAxis(ctx, params, "right");
};

OSD.drawRulers = function () {
const config = OSD.rulerConfig;
const init = OSD.initializeRulers();
if (!init) {
return;
}
const setup = OSD.setupRulerContext(init.canvas, init.preview, init.container, config);
if (!setup) {
return;
}
// Compose params for axis functions
const params = { ...setup, config };
OSD._drawTopAxis(params.ctx, params);
OSD._drawBottomAxis(params.ctx, params);
OSD._drawLeftAxis(params.ctx, params);
OSD._drawRightAxis(params.ctx, params);
};

OSD.drawByOrder = function (selectedPosition, field, charCode, x, y) {
// Check if there is other field at the same position
if (OSD.data.preview[selectedPosition] !== undefined) {
Expand Down Expand Up @@ -3937,6 +4167,9 @@ osd.initialize = function (callback) {
}
}

// Draw rulers after preview is rendered
OSD.drawRulers();

// Remove last tooltips
for (const tt of OSD.data.tooltips) {
tt.destroy();
Expand Down Expand Up @@ -3970,6 +4203,27 @@ osd.initialize = function (callback) {
});
});

// Rulers toggle
$("#osd-preview-rulers-selector").change(function () {
OSD.drawRulers();
});

// Window resize listener for rulers
$(window).on("resize.osd-rulers", function () {
if (document.querySelector("#osd-preview-rulers-selector")?.checked) {
// Throttle the resize calls using requestAnimationFrame
if (OSD.rulersResizeTimer) {
cancelAnimationFrame(OSD.rulersResizeTimer);
}
OSD.rulersResizeTimer = requestAnimationFrame(() => {
// Use setTimeout to ensure preview has been resized
setTimeout(() => {
OSD.drawRulers();
}, 100);
});
}
});

$("a.save").click(function () {
MSP.promise(MSPCodes.MSP_EEPROM_WRITE);
gui_log(i18n.getMessage("osdSettingsSaved"));
Expand Down Expand Up @@ -4090,6 +4344,7 @@ osd.cleanup = function (callback) {
// unbind "global" events
$(document).unbind("keypress");
$(document).off("click", "span.progressLabel a");
$(window).off("resize.osd-rulers");

if (callback) {
callback();
Expand Down
11 changes: 9 additions & 2 deletions src/tabs/osd.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,21 @@ <h1 class="tab_title">
</select>
<span class="osd-preview-zoom-group">
<input type="checkbox" id="osd-preview-zoom-selector" class="osd-preview-zoom-selector"/>
<label for="osd-preview-zoom-selector" i18n="osdSetupPreviewCheckZoom" />
<label for="osd-preview-zoom-selector" i18n="osdSetupPreviewCheckZoom"></label>
</span>
<span class="osd-preview-rulers-group">
<input type="checkbox" id="osd-preview-rulers-selector" class="osd-preview-rulers-selector" checked/>
<label for="osd-preview-rulers-selector" i18n="osdSetupPreviewCheckRulers"></label>
</span>
</span>
</div>
</div>

<div class="display-layout">
<div class="preview">
<div class="preview-container">
<canvas class="ruler-overlay" style="display: none;"></canvas>
<div class="preview">
</div>
</div>
</div>

Expand Down