add h7 support to spi ll driver, drop hal version
[inav.git] / src / main / io / osd_canvas.c
blob43e3b9b481d2778ebede1f81574c97c71f6c05dc
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.0) * 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) + osdConfig()->ahi_vertical_offset;
349 if (ahiY < 0) {
350 ahiY = 0;
352 if (ahiY + ahiHeight >= canvas->height) {
353 ahiY = canvas->height - ahiHeight - 1;
355 if (!configured) {
356 widgetAHIStyle_e ahiStyle = 0;
357 switch ((osd_ahi_style_e)osdConfig()->ahi_style) {
358 case OSD_AHI_STYLE_DEFAULT:
359 ahiStyle = DISPLAY_WIDGET_AHI_STYLE_STAIRCASE;
360 break;
361 case OSD_AHI_STYLE_LINE:
362 ahiStyle = DISPLAY_WIDGET_AHI_STYLE_LINE;
363 break;
365 widgetAHIConfiguration_t config = {
366 .rect.x = ahiX,
367 .rect.y = ahiY,
368 .rect.w = ahiWidth,
369 .rect.h = ahiHeight,
370 .style = ahiStyle,
371 .options = osdConfig()->ahi_bordered ? DISPLAY_WIDGET_AHI_OPTION_SHOW_CORNERS : 0,
372 .crosshairMargin = AHI_CROSSHAIR_MARGIN,
373 .strokeWidth = 1,
375 if (!displayWidgetsConfigureAHI(&widgets, instance, &config)) {
376 return false;
378 configured = true;
380 // The widget displays 270degs before fixing the bar at the top/bottom
381 // so that's 135degs each direction. Map that to the configured limit.
382 const float halfRange = 135.0f;
383 const float limit = halfRange / 180.0f * M_PIf;
384 float multiplier = osdConfig()->ahi_style == OSD_AHI_STYLE_DEFAULT ? 1.0f : halfRange / osdConfig()->ahi_max_pitch;
385 widgetAHIData_t data = {
386 .pitch = constrainf(pitchAngle * multiplier, -limit, limit),
387 .roll = rollAngle,
389 if (displayWidgetsDrawAHI(&widgets, instance, &data)) {
390 if (++iterations == 10) {
391 iterations = 0;
392 osdGridBufferClearPixelRect(canvas, ahiX, ahiY, ahiWidth, ahiHeight);
394 return true;
397 return false;
400 void osdCanvasDrawArtificialHorizon(displayPort_t *display, displayCanvas_t *canvas, const osdDrawPoint_t *p, float pitchAngle, float rollAngle)
402 UNUSED(display);
403 UNUSED(p);
405 static float prevPitchAngle = 9999;
406 static float prevRollAngle = 9999;
407 static timeMs_t nextDrawMaxMs = 0;
408 static timeMs_t nextDrawMinMs = 0;
410 timeMs_t now = millis();
412 float totalError = fabsf(prevPitchAngle - pitchAngle) + fabsf(prevRollAngle - rollAngle);
413 if ((now > nextDrawMinMs && totalError > 0.05f)|| now > nextDrawMaxMs) {
415 if (!osdCanvasDrawArtificialHorizonWidget(display, canvas, p, pitchAngle, rollAngle)) {
416 switch ((osd_ahi_style_e)osdConfig()->ahi_style) {
417 case OSD_AHI_STYLE_DEFAULT:
419 int x, y, w, h;
420 osdArtificialHorizonRect(canvas, &x, &y, &w, &h);
421 displayCanvasClearRect(canvas, x, y, w, h);
422 osdDrawArtificialHorizonShapes(canvas, pitchAngle, rollAngle);
423 break;
425 case OSD_AHI_STYLE_LINE:
426 osdDrawArtificialHorizonLine(canvas, prevPitchAngle, prevRollAngle, true);
427 osdDrawArtificialHorizonLine(canvas, pitchAngle, rollAngle, false);
428 break;
432 prevPitchAngle = pitchAngle;
433 prevRollAngle = rollAngle;
434 nextDrawMinMs = now + AHI_MIN_DRAW_INTERVAL_MS;
435 nextDrawMaxMs = now + AHI_MAX_DRAW_INTERVAL_MS;
439 void osdCanvasDrawHeadingGraph(displayPort_t *display, displayCanvas_t *canvas, const osdDrawPoint_t *p, int heading)
441 static const uint16_t graph[] = {
442 SYM_HEADING_W,
443 SYM_HEADING_LINE,
444 SYM_HEADING_DIVIDED_LINE,
445 SYM_HEADING_LINE,
446 SYM_HEADING_N,
447 SYM_HEADING_LINE,
448 SYM_HEADING_DIVIDED_LINE,
449 SYM_HEADING_LINE,
450 SYM_HEADING_E,
451 SYM_HEADING_LINE,
452 SYM_HEADING_DIVIDED_LINE,
453 SYM_HEADING_LINE,
454 SYM_HEADING_S,
455 SYM_HEADING_LINE,
456 SYM_HEADING_DIVIDED_LINE,
457 SYM_HEADING_LINE,
458 SYM_HEADING_W,
459 SYM_HEADING_LINE,
460 SYM_HEADING_DIVIDED_LINE,
461 SYM_HEADING_LINE,
462 SYM_HEADING_N,
463 SYM_HEADING_LINE,
464 SYM_HEADING_DIVIDED_LINE,
465 SYM_HEADING_LINE,
466 SYM_HEADING_E,
467 SYM_HEADING_LINE,
470 STATIC_ASSERT(sizeof(graph) > (3599 / OSD_HEADING_GRAPH_DECIDEGREES_PER_CHAR) + OSD_HEADING_GRAPH_WIDTH + 1, graph_is_too_short);
472 char buf[OSD_HEADING_GRAPH_WIDTH + 1];
473 int px;
474 int py;
476 osdDrawPointGetPixels(&px, &py, display, canvas, p);
477 int rw = OSD_HEADING_GRAPH_WIDTH * canvas->gridElementWidth;
478 int rh = canvas->gridElementHeight;
480 displayCanvasClipToRect(canvas, px, py, rw, rh);
482 int idx = heading / OSD_HEADING_GRAPH_DECIDEGREES_PER_CHAR;
483 int offset = ((heading % OSD_HEADING_GRAPH_DECIDEGREES_PER_CHAR) * canvas->gridElementWidth) / OSD_HEADING_GRAPH_DECIDEGREES_PER_CHAR;
484 memcpy_fn(buf, graph + idx, sizeof(buf) - 1);
485 buf[sizeof(buf) - 1] = '\0';
486 // We need a +1 because characters are 12px wide, so
487 // they can't have a 1px arrow centered. All existing fonts
488 // place the arrow at 5px, hence there's a 1px offset.
489 // TODO: Put this in font metadata and read it back.
490 displayCanvasDrawString(canvas, px - offset + 1, py, buf, DISPLAY_CANVAS_BITMAP_OPT_ERASE_TRANSPARENT);
492 displayCanvasSetStrokeColor(canvas, DISPLAY_CANVAS_COLOR_BLACK);
493 displayCanvasSetFillColor(canvas, DISPLAY_CANVAS_COLOR_WHITE);
494 int rmx = px + rw / 2;
495 displayCanvasFillStrokeTriangle(canvas, rmx - 2, py - 1, rmx + 2, py - 1, rmx, py + 1);
498 static int32_t osdCanvasSidebarGetValue(osd_sidebar_scroll_e scroll)
500 switch (scroll) {
501 case OSD_SIDEBAR_SCROLL_NONE:
502 break;
503 case OSD_SIDEBAR_SCROLL_ALTITUDE:
504 switch ((osd_unit_e)osdConfig()->units) {
505 case OSD_UNIT_UK:
506 FALLTHROUGH;
507 case OSD_UNIT_GA:
508 FALLTHROUGH;
509 case OSD_UNIT_IMPERIAL:
510 return CENTIMETERS_TO_CENTIFEET(osdGetAltitude());
511 case OSD_UNIT_METRIC_MPH:
512 FALLTHROUGH;
513 case OSD_UNIT_METRIC:
514 return osdGetAltitude();
516 break;
517 case OSD_SIDEBAR_SCROLL_SPEED:
519 #if defined(USE_GPS)
520 int speed = osdGetSpeedFromSelectedSource();
521 switch ((osd_unit_e)osdConfig()->units) {
522 case OSD_UNIT_UK:
523 FALLTHROUGH;
524 case OSD_UNIT_METRIC_MPH:
525 FALLTHROUGH;
526 case OSD_UNIT_IMPERIAL:
527 // cms/s to (mi/h) * 100
528 return speed * 224 / 100;
529 case OSD_UNIT_GA:
530 // cm/s to Knots * 100
531 return (int)(speed * 0.019438444924406) * 100;
532 case OSD_UNIT_METRIC:
533 // cm/s to (km/h) * 100
534 return speed * 36 / 10;
536 #endif
537 break;
539 case OSD_SIDEBAR_SCROLL_HOME_DISTANCE:
540 #if defined(USE_GPS)
541 switch ((osd_unit_e)osdConfig()->units) {
542 case OSD_UNIT_UK:
543 FALLTHROUGH;
544 case OSD_UNIT_GA:
545 FALLTHROUGH;
546 case OSD_UNIT_IMPERIAL:
547 return CENTIMETERS_TO_CENTIFEET(GPS_distanceToHome * 100);
548 case OSD_UNIT_METRIC_MPH:
549 FALLTHROUGH;
550 case OSD_UNIT_METRIC:
551 return GPS_distanceToHome * 100;
552 #endif
554 break;
556 return 0;
559 static uint8_t osdCanvasSidebarGetOptions(int *width, osd_sidebar_scroll_e scroll)
561 switch (scroll) {
562 case OSD_SIDEBAR_SCROLL_NONE:
563 break;
564 case OSD_SIDEBAR_SCROLL_ALTITUDE:
565 FALLTHROUGH;
566 case OSD_SIDEBAR_SCROLL_SPEED:
567 FALLTHROUGH;
568 case OSD_SIDEBAR_SCROLL_HOME_DISTANCE:
569 *width = OSD_CHAR_WIDTH * 5; // 4 numbers + unit
570 return 0;
572 *width = OSD_CHAR_WIDTH;
573 return DISPLAY_WIDGET_SIDEBAR_OPTION_STATIC | DISPLAY_WIDGET_SIDEBAR_OPTION_UNLABELED;
576 static void osdCanvasSidebarGetUnit(osdUnit_t *unit, uint16_t *countsPerStep, osd_sidebar_scroll_e scroll)
578 // We always count in 1/100 units, converting to
579 // "centifeet" when displaying imperial units
580 unit->scale = 100;
582 switch (scroll) {
583 case OSD_SIDEBAR_SCROLL_NONE:
584 unit->symbol = 0;
585 unit->divisor = 0;
586 unit->divided_symbol = 0;
587 *countsPerStep = 1;
588 break;
589 case OSD_SIDEBAR_SCROLL_ALTITUDE:
590 switch ((osd_unit_e)osdConfig()->units) {
591 case OSD_UNIT_UK:
592 FALLTHROUGH;
593 case OSD_UNIT_GA:
594 FALLTHROUGH;
595 case OSD_UNIT_IMPERIAL:
596 unit->symbol = SYM_ALT_FT;
597 unit->divisor = FEET_PER_KILOFEET;
598 unit->divided_symbol = SYM_ALT_KFT;
599 *countsPerStep = 50;
600 break;
601 case OSD_UNIT_METRIC_MPH:
602 FALLTHROUGH;
603 case OSD_UNIT_METRIC:
604 unit->symbol = SYM_ALT_M;
605 unit->divisor = METERS_PER_KILOMETER;
606 unit->divided_symbol = SYM_ALT_KM;
607 *countsPerStep = 20;
608 break;
610 break;
611 case OSD_SIDEBAR_SCROLL_SPEED:
612 switch ((osd_unit_e)osdConfig()->units) {
613 case OSD_UNIT_UK:
614 FALLTHROUGH;
615 case OSD_UNIT_METRIC_MPH:
616 FALLTHROUGH;
617 case OSD_UNIT_IMPERIAL:
618 unit->symbol = SYM_MPH;
619 unit->divisor = 0;
620 unit->divided_symbol = 0;
621 *countsPerStep = 5;
622 break;
623 case OSD_UNIT_GA:
624 unit->symbol = SYM_KT;
625 unit->divisor = 0;
626 unit->divided_symbol = 0;
627 *countsPerStep = 5;
628 break;
629 case OSD_UNIT_METRIC:
630 unit->symbol = SYM_KMH;
631 unit->divisor = 0;
632 unit->divided_symbol = 0;
633 *countsPerStep = 10;
634 break;
636 break;
637 case OSD_SIDEBAR_SCROLL_HOME_DISTANCE:
638 switch ((osd_unit_e)osdConfig()->units) {
639 case OSD_UNIT_UK:
640 FALLTHROUGH;
641 case OSD_UNIT_IMPERIAL:
642 unit->symbol = SYM_FT;
643 unit->divisor = FEET_PER_MILE;
644 unit->divided_symbol = SYM_MI;
645 *countsPerStep = 300;
646 break;
647 case OSD_UNIT_GA:
648 unit->symbol = SYM_FT;
649 unit->divisor = (int)FEET_PER_NAUTICALMILE;
650 unit->divided_symbol = SYM_NM;
651 *countsPerStep = 300;
652 break;
653 case OSD_UNIT_METRIC_MPH:
654 FALLTHROUGH;
655 case OSD_UNIT_METRIC:
656 unit->symbol = SYM_M;
657 unit->divisor = METERS_PER_KILOMETER;
658 unit->divided_symbol = SYM_KM;
659 *countsPerStep = 100;
660 break;
662 break;
666 static bool osdCanvasDrawSidebar(uint32_t *configured, displayWidgets_t *widgets,
667 displayCanvas_t *canvas,
668 int instance,
669 osd_sidebar_scroll_e scroll, unsigned scrollStep)
671 STATIC_ASSERT(OSD_SIDEBAR_SCROLL_MAX <= 3, adjust_scroll_shift);
672 STATIC_ASSERT(OSD_UNIT_MAX <= 5, adjust_units_shift);
673 // Configuration
674 uint32_t configuration = scrollStep << 16 | (unsigned)osdConfig()->sidebar_horizontal_offset << 8 | scroll << 6 | osdConfig()->units << 4;
675 if (configuration != *configured) {
676 int width;
677 uint8_t options = osdCanvasSidebarGetOptions(&width, scroll);
678 uint8_t ex;
679 uint8_t ey;
680 osdCrosshairPosition(&ex, &ey);
681 const int height = 2 * OSD_AH_SIDEBAR_HEIGHT_POS * OSD_CHAR_HEIGHT;
682 const int y = (ey - OSD_AH_SIDEBAR_HEIGHT_POS) * OSD_CHAR_HEIGHT;
684 widgetSidebarConfiguration_t config = {
685 .rect.y = y,
686 .rect.w = width,
687 .rect.h = height,
688 .options = options,
689 .divisions = OSD_AH_SIDEBAR_HEIGHT_POS * 2,
691 uint16_t countsPerStep = 0;
692 osdCanvasSidebarGetUnit(&config.unit, &countsPerStep, scroll);
694 int center = ex * OSD_CHAR_WIDTH;
695 int horizontalOffset = OSD_AH_SIDEBAR_WIDTH_POS * OSD_CHAR_WIDTH + osdConfig()->sidebar_horizontal_offset;
696 if (instance == WIDGET_SIDEBAR_LEFT_INSTANCE) {
697 config.rect.x = MAX(center - horizontalOffset - width, 0);
698 config.options |= DISPLAY_WIDGET_SIDEBAR_OPTION_LEFT;
699 } else {
700 config.rect.x = MIN(center + horizontalOffset, canvas->width - width - 1);
703 if (scrollStep > 0) {
704 countsPerStep = scrollStep;
706 config.counts_per_step = config.unit.scale * countsPerStep;
708 if (!displayWidgetsConfigureSidebar(widgets, instance, &config)) {
709 return false;
712 *configured = configuration;
714 // Actual drawing
715 int32_t data = osdCanvasSidebarGetValue(scroll);
716 return displayWidgetsDrawSidebar(widgets, instance, data);
719 bool osdCanvasDrawSidebars(displayPort_t *display, displayCanvas_t *canvas)
721 UNUSED(display);
723 static uint32_t leftConfigured = UINT32_MAX;
724 static uint32_t rightConfigured = UINT32_MAX;
725 static timeMs_t nextRedraw = 0;
727 timeMs_t now = millis();
729 if (now < nextRedraw) {
730 return true;
733 displayWidgets_t widgets;
734 if (displayCanvasGetWidgets(&widgets, canvas)) {
735 if (!osdCanvasDrawSidebar(&leftConfigured, &widgets, canvas,
736 WIDGET_SIDEBAR_LEFT_INSTANCE,
737 osdConfig()->left_sidebar_scroll,
738 osdConfig()->left_sidebar_scroll_step)) {
739 return false;
741 if (!osdCanvasDrawSidebar(&rightConfigured, &widgets, canvas,
742 WIDGET_SIDEBAR_RIGHT_INSTANCE,
743 osdConfig()->right_sidebar_scroll,
744 osdConfig()->right_sidebar_scroll_step)) {
745 return false;
747 nextRedraw = now + SIDEBAR_REDRAW_INTERVAL_MS;
748 return true;
750 return false;
753 #endif