updates
[inav.git] / src / main / io / osd_canvas.c
blob47dd4ee47bcd0357f0ec65ce20f13bf8f7d2141c
1 /*
2 * This file is part of INAV.
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
6 * You can obtain one at http://mozilla.org/MPL/2.0/.
8 * Alternatively, the contents of this file may be used under the terms
9 * of the GNU General Public License Version 3, as described below:
11 * This file is free software: you may copy, redistribute and/or modify
12 * it under the terms of the GNU General Public License as published by the
13 * Free Software Foundation, either version 3 of the License, or (at your
14 * option) any later version.
16 * This file is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
19 * Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see http://www.gnu.org/licenses/.
24 * @author Alberto Garcia Hierro <alberto@garciahierro.com>
27 #include <math.h>
29 #include "platform.h"
31 #if defined(USE_CANVAS)
33 #define AHI_MIN_DRAW_INTERVAL_MS 50
34 #define AHI_MAX_DRAW_INTERVAL_MS 1000
35 #define AHI_CROSSHAIR_MARGIN 6
37 #define SIDEBAR_REDRAW_INTERVAL_MS 100
38 #define WIDGET_SIDEBAR_LEFT_INSTANCE 0
39 #define WIDGET_SIDEBAR_RIGHT_INSTANCE 1
41 #include "common/constants.h"
42 #include "common/log.h"
43 #include "common/maths.h"
44 #include "common/typeconversion.h"
45 #include "common/utils.h"
47 #include "drivers/display.h"
48 #include "drivers/display_canvas.h"
49 #include "drivers/display_widgets.h"
50 #include "drivers/osd.h"
51 #include "drivers/osd_symbols.h"
52 #include "drivers/time.h"
54 #include "io/osd_common.h"
55 #include "io/osd.h"
57 #include "navigation/navigation.h"
59 #define OSD_CANVAS_VARIO_ARROWS_PER_SLOT 2.0f
61 static void osdCanvasVarioRect(int *y, int *h, displayCanvas_t *canvas, int midY, float zvel)
63 int maxHeight = ceilf(OSD_VARIO_HEIGHT_ROWS /OSD_CANVAS_VARIO_ARROWS_PER_SLOT) * canvas->gridElementHeight;
64 // We use font characters with 2 arrows per slot
65 int height = MIN(fabsf(zvel) / (OSD_VARIO_CM_S_PER_ARROW * OSD_CANVAS_VARIO_ARROWS_PER_SLOT) * canvas->gridElementHeight, maxHeight);
66 if (zvel >= 0) {
67 *y = midY - height;
68 } else {
69 *y = midY;
71 *h = height;
74 void osdCanvasDrawVario(displayPort_t *display, displayCanvas_t *canvas, const osdDrawPoint_t *p, float zvel)
76 UNUSED(display);
78 static float prev = 0;
80 if (fabsf(prev - zvel) < (OSD_VARIO_CM_S_PER_ARROW / (OSD_CANVAS_VARIO_ARROWS_PER_SLOT * 10))) {
81 return;
84 // Make sure we clear the grid buffer
85 uint8_t gx;
86 uint8_t gy;
87 osdDrawPointGetGrid(&gx, &gy, display, canvas, p);
88 osdGridBufferClearGridRect(gx, gy, 1, OSD_VARIO_HEIGHT_ROWS);
90 int midY = gy * canvas->gridElementHeight + (OSD_VARIO_HEIGHT_ROWS * canvas->gridElementHeight) / 2;
91 // Make sure we're aligned with the center-ish of the grid based variant
92 midY -= canvas->gridElementHeight;
94 int x = gx * canvas->gridElementWidth;
95 int w = canvas->gridElementWidth;
96 int y;
97 int h;
99 osdCanvasVarioRect(&y, &h, canvas, midY, zvel);
101 if (signbit(prev) != signbit(zvel) || fabsf(prev) > fabsf(zvel)) {
102 // New rectangle doesn't overwrite the old one, we need to erase
103 int py;
104 int ph;
105 osdCanvasVarioRect(&py, &ph, canvas, midY, prev);
106 if (ph != h) {
107 displayCanvasClearRect(canvas, x, py, w, ph);
110 displayCanvasClipToRect(canvas, x, y, w, h);
111 if (zvel > 0) {
112 for (int yy = midY - canvas->gridElementHeight; yy > midY - h - canvas->gridElementHeight; yy -= canvas->gridElementHeight) {
113 displayCanvasDrawCharacter(canvas, x, yy, SYM_VARIO_UP_2A, DISPLAY_CANVAS_BITMAP_OPT_ERASE_TRANSPARENT);
115 } else {
116 for (int yy = midY; yy < midY + h; yy += canvas->gridElementHeight) {
117 displayCanvasDrawCharacter(canvas, x, yy, SYM_VARIO_DOWN_2A, DISPLAY_CANVAS_BITMAP_OPT_ERASE_TRANSPARENT);
120 prev = zvel;
123 void osdCanvasDrawDirArrow(displayPort_t *display, displayCanvas_t *canvas, const osdDrawPoint_t *p, float degrees)
125 UNUSED(display);
127 const int top = 6;
128 const int topInset = -2;
129 const int bottom = -6;
130 const int width = 5;
131 // Since grid slots are not square, when we rotate the arrow
132 // it overflows horizontally a bit. Making a square-ish arrow
133 // doesn't look good, so it's better to overstep the grid slot
134 // boundaries a bit and then clean after ourselves.
135 const int overflow = 3;
137 int px;
138 int py;
139 osdDrawPointGetPixels(&px, &py, display, canvas, p);
141 displayCanvasClearRect(canvas, px - overflow, py, canvas->gridElementWidth + overflow * 2, canvas->gridElementHeight);
143 displayCanvasSetFillColor(canvas, DISPLAY_CANVAS_COLOR_WHITE);
144 displayCanvasSetStrokeColor(canvas, DISPLAY_CANVAS_COLOR_BLACK);
146 displayCanvasCtmRotate(canvas, -DEGREES_TO_RADIANS(180 + degrees));
147 displayCanvasCtmTranslate(canvas, px + canvas->gridElementWidth / 2, py + canvas->gridElementHeight / 2);
149 // Main triangle
150 displayCanvasFillStrokeTriangle(canvas, 0, top, width, bottom, -width, bottom);
151 // Inset triangle
152 displayCanvasSetFillColor(canvas, DISPLAY_CANVAS_COLOR_TRANSPARENT);
153 displayCanvasFillTriangle(canvas, 0, topInset, width + 1, bottom - 1, -width, bottom - 1);
154 // Draw bottom strokes of the triangle
155 displayCanvasMoveToPoint(canvas, -width, bottom - 1);
156 displayCanvasStrokeLineToPoint(canvas, 0, topInset);
157 displayCanvasStrokeLineToPoint(canvas, width, bottom - 1);
160 static void osdDrawArtificialHorizonLevelLine(displayCanvas_t *canvas, int width, int pos, int margin)
162 displayCanvasSetLineOutlineType(canvas, DISPLAY_CANVAS_OUTLINE_TYPE_BOTTOM);
164 int yoff = pos >= 0 ? 10 : -10;
165 int yc = -pos - 1;
166 int sz = width / 2;
168 // Horizontal strokes
169 displayCanvasMoveToPoint(canvas, -sz, yc);
170 displayCanvasStrokeLineToPoint(canvas, -margin, yc);
171 displayCanvasMoveToPoint(canvas, sz, yc);
172 displayCanvasStrokeLineToPoint(canvas, margin, yc);
174 // Vertical strokes
175 displayCanvasSetLineOutlineType(canvas, DISPLAY_CANVAS_OUTLINE_TYPE_LEFT);
176 displayCanvasMoveToPoint(canvas, -sz, yc);
177 displayCanvasStrokeLineToPoint(canvas, -sz, yc + yoff);
178 displayCanvasSetLineOutlineType(canvas, DISPLAY_CANVAS_OUTLINE_TYPE_RIGHT);
179 displayCanvasMoveToPoint(canvas, sz, yc);
180 displayCanvasStrokeLineToPoint(canvas, sz, yc + yoff);
183 static void osdArtificialHorizonRect(displayCanvas_t *canvas, int *lx, int *ty, int *w, int *h)
185 *w = (OSD_AHI_WIDTH + 1) * canvas->gridElementWidth;
186 *h = OSD_AHI_HEIGHT * canvas->gridElementHeight;
188 *lx = (canvas->width - *w) / 2;
189 *ty = (canvas->height - *h) / 2;
192 static void osdDrawArtificialHorizonShapes(displayCanvas_t *canvas, float pitchAngle, float rollAngle)
194 int barWidth = (OSD_AHI_WIDTH - 1) * canvas->gridElementWidth;
195 int levelBarWidth = barWidth * (3.0/4);
196 float pixelsPerDegreeLevel = 3.5f;
197 int borderSize = 3;
198 char buf[12];
200 int lx;
201 int ty;
202 int maxWidth;
203 int maxHeight;
205 osdArtificialHorizonRect(canvas, &lx, &ty, &maxWidth, &maxHeight);
207 displayCanvasContextPush(canvas);
209 int rx = lx + maxWidth;
210 int by = ty + maxHeight;
212 displayCanvasSetStrokeColor(canvas, DISPLAY_CANVAS_COLOR_BLACK);
214 displayCanvasMoveToPoint(canvas, lx, ty + borderSize);
215 displayCanvasStrokeLineToPoint(canvas, lx, ty);
216 displayCanvasStrokeLineToPoint(canvas, lx + borderSize, ty);
218 displayCanvasMoveToPoint(canvas, rx, ty + borderSize);
219 displayCanvasStrokeLineToPoint(canvas, rx, ty);
220 displayCanvasStrokeLineToPoint(canvas, rx - borderSize, ty);
222 displayCanvasMoveToPoint(canvas,lx, by - borderSize);
223 displayCanvasStrokeLineToPoint(canvas, lx, by);
224 displayCanvasStrokeLineToPoint(canvas, lx + borderSize, by);
226 displayCanvasMoveToPoint(canvas, rx, by - borderSize);
227 displayCanvasStrokeLineToPoint(canvas, rx, by);
228 displayCanvasStrokeLineToPoint(canvas, rx - borderSize, by);
230 displayCanvasClipToRect(canvas, lx + 1, ty + 1, maxWidth - 2, maxHeight - 2);
231 osdGridBufferClearPixelRect(canvas, lx, ty, maxWidth, maxHeight);
233 displayCanvasSetStrokeColor(canvas, DISPLAY_CANVAS_COLOR_WHITE);
234 displayCanvasSetLineOutlineColor(canvas, DISPLAY_CANVAS_COLOR_BLACK);
236 // The draw just the 5 bars closest to the current pitch level
237 float pitchDegrees = RADIANS_TO_DEGREES(pitchAngle);
238 float pitchCenter = roundf(pitchDegrees / 10.0f);
239 float pitchOffset = -pitchDegrees * pixelsPerDegreeLevel;
240 float translateX = canvas->width / 2;
241 float translateY = canvas->height / 2;
243 displayCanvasCtmTranslate(canvas, 0, pitchOffset);
244 displayCanvasContextPush(canvas);
245 displayCanvasCtmRotate(canvas, rollAngle);
247 displayCanvasCtmTranslate(canvas, translateX, translateY);
249 for (int ii = pitchCenter - 2; ii <= pitchCenter + 2; ii++) {
250 if (ii == 0) {
251 displayCanvasSetLineOutlineType(canvas, DISPLAY_CANVAS_OUTLINE_TYPE_BOTTOM);
252 displayCanvasMoveToPoint(canvas, -barWidth / 2, 0);
253 displayCanvasStrokeLineToPoint(canvas, -AHI_CROSSHAIR_MARGIN, 0);
254 displayCanvasMoveToPoint(canvas, barWidth / 2, 0);
255 displayCanvasStrokeLineToPoint(canvas, AHI_CROSSHAIR_MARGIN, 0);
256 continue;
259 int pos = ii * 10 * pixelsPerDegreeLevel;
260 int margin = (ii > 9 || ii < -9) ? 9 : 6;
261 osdDrawArtificialHorizonLevelLine(canvas, levelBarWidth, -pos, margin);
264 displayCanvasContextPop(canvas);
266 displayCanvasCtmTranslate(canvas, translateX, translateY);
267 displayCanvasCtmScale(canvas, 0.5f, 0.5f);
269 // Draw line labels
270 float sx = sin_approx(rollAngle);
271 float sy = cos_approx(-rollAngle);
272 for (int ii = pitchCenter - 2; ii <= pitchCenter + 2; ii++) {
273 if (ii == 0) {
274 continue;
277 int level = ii * 10;
278 int absLevel = ABS(level);
279 itoa(absLevel, buf, 10);
280 int pos = level * pixelsPerDegreeLevel;
281 int charY = 9 - pos * 2;
282 int cx = (absLevel >= 100 ? -1.5f : -1.0f) * canvas->gridElementWidth;
283 int px = cx + (pitchOffset + pos) * sx * 2;
284 int py = -charY - (pitchOffset + pos) * (1 - sy) * 2;
285 displayCanvasDrawString(canvas, px, py, buf, 0);
287 displayCanvasContextPop(canvas);
290 void osdDrawArtificialHorizonLine(displayCanvas_t *canvas, float pitchAngle, float rollAngle, bool erase)
292 int barWidth = (OSD_AHI_WIDTH - 1) * canvas->gridElementWidth;
293 int maxHeight = canvas->height;
294 float pixelsPerDegreeLevel = 1.5f;
295 int maxWidth = (OSD_AHI_WIDTH + 1) * canvas->gridElementWidth;
297 int lx = (canvas->width - maxWidth) / 2;
299 displayCanvasContextPush(canvas);
301 displayCanvasClipToRect(canvas, lx, 0, maxWidth, maxHeight);
302 osdGridBufferClearPixelRect(canvas, lx, 0, maxWidth, maxHeight);
304 if (erase) {
305 displayCanvasSetStrokeColor(canvas, DISPLAY_CANVAS_COLOR_TRANSPARENT);
306 displayCanvasSetLineOutlineColor(canvas, DISPLAY_CANVAS_COLOR_TRANSPARENT);
307 } else {
308 displayCanvasSetStrokeColor(canvas, DISPLAY_CANVAS_COLOR_WHITE);
309 displayCanvasSetLineOutlineColor(canvas, DISPLAY_CANVAS_COLOR_BLACK);
312 float pitchDegrees = RADIANS_TO_DEGREES(pitchAngle);
313 float pitchOffset = -pitchDegrees * pixelsPerDegreeLevel;
314 float translateX = canvas->width / 2;
315 float translateY = canvas->height / 2;
317 displayCanvasCtmTranslate(canvas, 0, pitchOffset);
318 displayCanvasCtmRotate(canvas, rollAngle);
319 displayCanvasCtmTranslate(canvas, translateX, translateY);
322 displayCanvasSetStrokeWidth(canvas, 2);
323 displayCanvasSetLineOutlineType(canvas, DISPLAY_CANVAS_OUTLINE_TYPE_BOTTOM);
324 displayCanvasMoveToPoint(canvas, -barWidth / 2, 0);
325 displayCanvasStrokeLineToPoint(canvas, -AHI_CROSSHAIR_MARGIN, 0);
326 displayCanvasMoveToPoint(canvas, barWidth / 2, 0);
327 displayCanvasStrokeLineToPoint(canvas, AHI_CROSSHAIR_MARGIN, 0);
329 displayCanvasContextPop(canvas);
332 static bool osdCanvasDrawArtificialHorizonWidget(displayPort_t *display, displayCanvas_t *canvas, const osdDrawPoint_t *p, float pitchAngle, float rollAngle)
334 UNUSED(display);
335 UNUSED(p);
337 const int instance = 0;
338 static int iterations = 0;
339 static bool configured = false;
341 displayWidgets_t widgets;
342 if (displayCanvasGetWidgets(&widgets, canvas) &&
343 displayWidgetsSupportedInstances(&widgets, DISPLAY_WIDGET_TYPE_AHI) >= instance) {
345 int ahiWidth = osdConfig()->ahi_width;
346 int ahiX = (canvas->width - ahiWidth) / 2;
347 int ahiHeight = osdConfig()->ahi_height;
348 int ahiY = ((canvas->height - ahiHeight) / 2);
349 ahiY += osdConfig()->ahi_vertical_offset;
351 if (ahiY < 0) {
352 ahiY = 0;
354 if (ahiY + ahiHeight >= canvas->height) {
355 ahiY = canvas->height - ahiHeight - 1;
357 if (!configured) {
358 widgetAHIStyle_e ahiStyle = 0;
359 switch ((osd_ahi_style_e)osdConfig()->ahi_style) {
360 case OSD_AHI_STYLE_DEFAULT:
361 ahiStyle = DISPLAY_WIDGET_AHI_STYLE_STAIRCASE;
362 break;
363 case OSD_AHI_STYLE_LINE:
364 ahiStyle = DISPLAY_WIDGET_AHI_STYLE_LINE;
365 break;
367 widgetAHIConfiguration_t config = {
368 .rect.x = ahiX,
369 .rect.y = ahiY,
370 .rect.w = ahiWidth,
371 .rect.h = ahiHeight,
372 .style = ahiStyle,
373 .options = osdConfig()->ahi_bordered ? DISPLAY_WIDGET_AHI_OPTION_SHOW_CORNERS : 0,
374 .crosshairMargin = AHI_CROSSHAIR_MARGIN,
375 .strokeWidth = 1,
377 if (!displayWidgetsConfigureAHI(&widgets, instance, &config)) {
378 return false;
380 configured = true;
382 // The widget displays 270degs before fixing the bar at the top/bottom
383 // so that's 135degs each direction. Map that to the configured limit.
384 const float halfRange = 135.0f;
385 const float limit = halfRange / 180.0f * M_PIf;
386 float multiplier = osdConfig()->ahi_style == OSD_AHI_STYLE_DEFAULT ? 1.0f : halfRange / osdConfig()->ahi_max_pitch;
387 widgetAHIData_t data = {
388 .pitch = constrainf(pitchAngle * multiplier, -limit, limit),
389 .roll = rollAngle,
391 if (displayWidgetsDrawAHI(&widgets, instance, &data)) {
392 if (++iterations == 10) {
393 iterations = 0;
394 osdGridBufferClearPixelRect(canvas, ahiX, ahiY, ahiWidth, ahiHeight);
396 return true;
399 return false;
402 void osdCanvasDrawArtificialHorizon(displayPort_t *display, displayCanvas_t *canvas, const osdDrawPoint_t *p, float pitchAngle, float rollAngle)
404 UNUSED(display);
405 UNUSED(p);
407 static float prevPitchAngle = 9999;
408 static float prevRollAngle = 9999;
409 static timeMs_t nextDrawMaxMs = 0;
410 static timeMs_t nextDrawMinMs = 0;
412 timeMs_t now = millis();
414 float totalError = fabsf(prevPitchAngle - pitchAngle) + fabsf(prevRollAngle - rollAngle);
415 if ((now > nextDrawMinMs && totalError > 0.05f)|| now > nextDrawMaxMs) {
417 if (!osdCanvasDrawArtificialHorizonWidget(display, canvas, p, pitchAngle, rollAngle)) {
418 switch ((osd_ahi_style_e)osdConfig()->ahi_style) {
419 case OSD_AHI_STYLE_DEFAULT:
421 int x, y, w, h;
422 osdArtificialHorizonRect(canvas, &x, &y, &w, &h);
423 displayCanvasClearRect(canvas, x, y, w, h);
424 osdDrawArtificialHorizonShapes(canvas, pitchAngle, rollAngle);
425 break;
427 case OSD_AHI_STYLE_LINE:
428 osdDrawArtificialHorizonLine(canvas, prevPitchAngle, prevRollAngle, true);
429 osdDrawArtificialHorizonLine(canvas, pitchAngle, rollAngle, false);
430 break;
434 prevPitchAngle = pitchAngle;
435 prevRollAngle = rollAngle;
436 nextDrawMinMs = now + AHI_MIN_DRAW_INTERVAL_MS;
437 nextDrawMaxMs = now + AHI_MAX_DRAW_INTERVAL_MS;
441 void osdCanvasDrawHeadingGraph(displayPort_t *display, displayCanvas_t *canvas, const osdDrawPoint_t *p, int heading)
443 static const uint8_t graph[] = {
444 SYM_HEADING_W,
445 SYM_HEADING_LINE,
446 SYM_HEADING_DIVIDED_LINE,
447 SYM_HEADING_LINE,
448 SYM_HEADING_N,
449 SYM_HEADING_LINE,
450 SYM_HEADING_DIVIDED_LINE,
451 SYM_HEADING_LINE,
452 SYM_HEADING_E,
453 SYM_HEADING_LINE,
454 SYM_HEADING_DIVIDED_LINE,
455 SYM_HEADING_LINE,
456 SYM_HEADING_S,
457 SYM_HEADING_LINE,
458 SYM_HEADING_DIVIDED_LINE,
459 SYM_HEADING_LINE,
460 SYM_HEADING_W,
461 SYM_HEADING_LINE,
462 SYM_HEADING_DIVIDED_LINE,
463 SYM_HEADING_LINE,
464 SYM_HEADING_N,
465 SYM_HEADING_LINE,
466 SYM_HEADING_DIVIDED_LINE,
467 SYM_HEADING_LINE,
468 SYM_HEADING_E,
469 SYM_HEADING_LINE,
472 STATIC_ASSERT(sizeof(graph) > (3599 / OSD_HEADING_GRAPH_DECIDEGREES_PER_CHAR) + OSD_HEADING_GRAPH_WIDTH + 1, graph_is_too_short);
474 char buf[OSD_HEADING_GRAPH_WIDTH + 1];
475 int px;
476 int py;
478 osdDrawPointGetPixels(&px, &py, display, canvas, p);
479 int rw = OSD_HEADING_GRAPH_WIDTH * canvas->gridElementWidth;
480 int rh = canvas->gridElementHeight;
482 displayCanvasClipToRect(canvas, px, py, rw, rh);
484 int idx = heading / OSD_HEADING_GRAPH_DECIDEGREES_PER_CHAR;
485 int offset = ((heading % OSD_HEADING_GRAPH_DECIDEGREES_PER_CHAR) * canvas->gridElementWidth) / OSD_HEADING_GRAPH_DECIDEGREES_PER_CHAR;
486 memcpy_fn(buf, graph + idx, sizeof(buf) - 1);
487 buf[sizeof(buf) - 1] = '\0';
488 // We need a +1 because characters are 12px wide, so
489 // they can't have a 1px arrow centered. All existing fonts
490 // place the arrow at 5px, hence there's a 1px offset.
491 // TODO: Put this in font metadata and read it back.
492 displayCanvasDrawString(canvas, px - offset + 1, py, buf, DISPLAY_CANVAS_BITMAP_OPT_ERASE_TRANSPARENT);
494 displayCanvasSetStrokeColor(canvas, DISPLAY_CANVAS_COLOR_BLACK);
495 displayCanvasSetFillColor(canvas, DISPLAY_CANVAS_COLOR_WHITE);
496 int rmx = px + rw / 2;
497 displayCanvasFillStrokeTriangle(canvas, rmx - 2, py - 1, rmx + 2, py - 1, rmx, py + 1);
500 static int32_t osdCanvasSidebarGetValue(osd_sidebar_scroll_e scroll)
502 switch (scroll) {
503 case OSD_SIDEBAR_SCROLL_NONE:
504 break;
505 case OSD_SIDEBAR_SCROLL_ALTITUDE:
506 switch ((osd_unit_e)osdConfig()->units) {
507 case OSD_UNIT_UK:
508 FALLTHROUGH;
509 case OSD_UNIT_GA:
510 FALLTHROUGH;
511 case OSD_UNIT_IMPERIAL:
512 return CENTIMETERS_TO_CENTIFEET(osdGetAltitude());
513 case OSD_UNIT_METRIC_MPH:
514 FALLTHROUGH;
515 case OSD_UNIT_METRIC:
516 return osdGetAltitude();
518 break;
519 case OSD_SIDEBAR_SCROLL_SPEED:
521 #if defined(USE_GPS)
522 int16_t speed = osdGetSpeedFromSelectedSource();
523 switch ((osd_unit_e)osdConfig()->units) {
524 case OSD_UNIT_UK:
525 FALLTHROUGH;
526 case OSD_UNIT_METRIC_MPH:
527 FALLTHROUGH;
528 case OSD_UNIT_IMPERIAL:
529 // cms/s to (mi/h) * 100
530 return speed * 224 / 100;
531 case OSD_UNIT_GA:
532 // cm/s to Knots * 100
533 return (int)(speed * 0.019438444924406) * 100;
534 case OSD_UNIT_METRIC:
535 // cm/s to (km/h) * 100
536 return speed * 36 / 10;
538 #endif
539 break;
541 case OSD_SIDEBAR_SCROLL_HOME_DISTANCE:
542 #if defined(USE_GPS)
543 switch ((osd_unit_e)osdConfig()->units) {
544 case OSD_UNIT_UK:
545 FALLTHROUGH;
546 case OSD_UNIT_GA:
547 FALLTHROUGH;
548 case OSD_UNIT_IMPERIAL:
549 return CENTIMETERS_TO_CENTIFEET(GPS_distanceToHome * 100);
550 case OSD_UNIT_METRIC_MPH:
551 FALLTHROUGH;
552 case OSD_UNIT_METRIC:
553 return GPS_distanceToHome * 100;
554 #endif
556 break;
558 return 0;
561 static uint8_t osdCanvasSidebarGetOptions(int *width, osd_sidebar_scroll_e scroll)
563 switch (scroll) {
564 case OSD_SIDEBAR_SCROLL_NONE:
565 break;
566 case OSD_SIDEBAR_SCROLL_ALTITUDE:
567 FALLTHROUGH;
568 case OSD_SIDEBAR_SCROLL_SPEED:
569 FALLTHROUGH;
570 case OSD_SIDEBAR_SCROLL_HOME_DISTANCE:
571 *width = OSD_CHAR_WIDTH * 5; // 4 numbers + unit
572 return 0;
574 *width = OSD_CHAR_WIDTH;
575 return DISPLAY_WIDGET_SIDEBAR_OPTION_STATIC | DISPLAY_WIDGET_SIDEBAR_OPTION_UNLABELED;
578 static void osdCanvasSidebarGetUnit(osdUnit_t *unit, uint16_t *countsPerStep, osd_sidebar_scroll_e scroll)
580 // We always count in 1/100 units, converting to
581 // "centifeet" when displaying imperial units
582 unit->scale = 100;
584 switch (scroll) {
585 case OSD_SIDEBAR_SCROLL_NONE:
586 unit->symbol = 0;
587 unit->divisor = 0;
588 unit->divided_symbol = 0;
589 *countsPerStep = 1;
590 break;
591 case OSD_SIDEBAR_SCROLL_ALTITUDE:
592 switch ((osd_unit_e)osdConfig()->units) {
593 case OSD_UNIT_UK:
594 FALLTHROUGH;
595 case OSD_UNIT_GA:
596 FALLTHROUGH;
597 case OSD_UNIT_IMPERIAL:
598 unit->symbol = SYM_ALT_FT;
599 unit->divisor = FEET_PER_KILOFEET;
600 unit->divided_symbol = SYM_ALT_KFT;
601 *countsPerStep = 50;
602 break;
603 case OSD_UNIT_METRIC_MPH:
604 FALLTHROUGH;
605 case OSD_UNIT_METRIC:
606 unit->symbol = SYM_ALT_M;
607 unit->divisor = METERS_PER_KILOMETER;
608 unit->divided_symbol = SYM_ALT_KM;
609 *countsPerStep = 20;
610 break;
612 break;
613 case OSD_SIDEBAR_SCROLL_SPEED:
614 switch ((osd_unit_e)osdConfig()->units) {
615 case OSD_UNIT_UK:
616 FALLTHROUGH;
617 case OSD_UNIT_METRIC_MPH:
618 FALLTHROUGH;
619 case OSD_UNIT_IMPERIAL:
620 unit->symbol = SYM_MPH;
621 unit->divisor = 0;
622 unit->divided_symbol = 0;
623 *countsPerStep = 5;
624 break;
625 case OSD_UNIT_GA:
626 unit->symbol = SYM_KT;
627 unit->divisor = 0;
628 unit->divided_symbol = 0;
629 *countsPerStep = 5;
630 break;
631 case OSD_UNIT_METRIC:
632 unit->symbol = SYM_KMH;
633 unit->divisor = 0;
634 unit->divided_symbol = 0;
635 *countsPerStep = 10;
636 break;
638 break;
639 case OSD_SIDEBAR_SCROLL_HOME_DISTANCE:
640 switch ((osd_unit_e)osdConfig()->units) {
641 case OSD_UNIT_UK:
642 FALLTHROUGH;
643 case OSD_UNIT_IMPERIAL:
644 unit->symbol = SYM_FT;
645 unit->divisor = FEET_PER_MILE;
646 unit->divided_symbol = SYM_MI;
647 *countsPerStep = 300;
648 break;
649 case OSD_UNIT_GA:
650 unit->symbol = SYM_FT;
651 unit->divisor = (int)FEET_PER_NAUTICALMILE;
652 unit->divided_symbol = SYM_NM;
653 *countsPerStep = 300;
654 break;
655 case OSD_UNIT_METRIC_MPH:
656 FALLTHROUGH;
657 case OSD_UNIT_METRIC:
658 unit->symbol = SYM_M;
659 unit->divisor = METERS_PER_KILOMETER;
660 unit->divided_symbol = SYM_KM;
661 *countsPerStep = 100;
662 break;
664 break;
668 static bool osdCanvasDrawSidebar(uint32_t *configured, displayWidgets_t *widgets,
669 displayCanvas_t *canvas,
670 int instance,
671 osd_sidebar_scroll_e scroll, unsigned scrollStep)
673 STATIC_ASSERT(OSD_SIDEBAR_SCROLL_MAX <= 3, adjust_scroll_shift);
674 STATIC_ASSERT(OSD_UNIT_MAX <= 5, adjust_units_shift);
675 // Configuration
676 uint32_t configuration = scrollStep << 16 | (unsigned)osdConfig()->sidebar_horizontal_offset << 8 | scroll << 6 | osdConfig()->units << 4;
677 if (configuration != *configured) {
678 int width;
679 uint8_t options = osdCanvasSidebarGetOptions(&width, scroll);
680 uint8_t ex;
681 uint8_t ey;
682 osdCrosshairPosition(&ex, &ey);
683 const int height = 2 * OSD_AH_SIDEBAR_HEIGHT_POS * OSD_CHAR_HEIGHT;
684 const int y = (ey - OSD_AH_SIDEBAR_HEIGHT_POS) * OSD_CHAR_HEIGHT;
686 widgetSidebarConfiguration_t config = {
687 .rect.y = y,
688 .rect.w = width,
689 .rect.h = height,
690 .options = options,
691 .divisions = OSD_AH_SIDEBAR_HEIGHT_POS * 2,
693 uint16_t countsPerStep = 0;
694 osdCanvasSidebarGetUnit(&config.unit, &countsPerStep, scroll);
696 int center = ex * OSD_CHAR_WIDTH;
697 int horizontalOffset = OSD_AH_SIDEBAR_WIDTH_POS * OSD_CHAR_WIDTH + osdConfig()->sidebar_horizontal_offset;
698 if (instance == WIDGET_SIDEBAR_LEFT_INSTANCE) {
699 config.rect.x = MAX(center - horizontalOffset - width, 0);
700 config.options |= DISPLAY_WIDGET_SIDEBAR_OPTION_LEFT;
701 } else {
702 config.rect.x = MIN(center + horizontalOffset, canvas->width - width - 1);
705 if (scrollStep > 0) {
706 countsPerStep = scrollStep;
708 config.counts_per_step = config.unit.scale * countsPerStep;
710 if (!displayWidgetsConfigureSidebar(widgets, instance, &config)) {
711 return false;
714 *configured = configuration;
716 // Actual drawing
717 int32_t data = osdCanvasSidebarGetValue(scroll);
718 return displayWidgetsDrawSidebar(widgets, instance, data);
721 bool osdCanvasDrawSidebars(displayPort_t *display, displayCanvas_t *canvas)
723 UNUSED(display);
725 static uint32_t leftConfigured = UINT32_MAX;
726 static uint32_t rightConfigured = UINT32_MAX;
727 static timeMs_t nextRedraw = 0;
729 timeMs_t now = millis();
731 if (now < nextRedraw) {
732 return true;
735 displayWidgets_t widgets;
736 if (displayCanvasGetWidgets(&widgets, canvas)) {
737 if (!osdCanvasDrawSidebar(&leftConfigured, &widgets, canvas,
738 WIDGET_SIDEBAR_LEFT_INSTANCE,
739 osdConfig()->left_sidebar_scroll,
740 osdConfig()->left_sidebar_scroll_step)) {
741 return false;
743 if (!osdCanvasDrawSidebar(&rightConfigured, &widgets, canvas,
744 WIDGET_SIDEBAR_RIGHT_INSTANCE,
745 osdConfig()->right_sidebar_scroll,
746 osdConfig()->right_sidebar_scroll_step)) {
747 return false;
749 nextRedraw = now + SIDEBAR_REDRAW_INTERVAL_MS;
750 return true;
752 return false;
755 #endif