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>
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"
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
);
74 void osdCanvasDrawVario(displayPort_t
*display
, displayCanvas_t
*canvas
, const osdDrawPoint_t
*p
, float zvel
)
78 static float prev
= 0;
80 if (fabsf(prev
- zvel
) < (OSD_VARIO_CM_S_PER_ARROW
/ (OSD_CANVAS_VARIO_ARROWS_PER_SLOT
* 10))) {
84 // Make sure we clear the grid buffer
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
;
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
105 osdCanvasVarioRect(&py
, &ph
, canvas
, midY
, prev
);
107 displayCanvasClearRect(canvas
, x
, py
, w
, ph
);
110 displayCanvasClipToRect(canvas
, x
, y
, w
, h
);
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
);
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
);
123 void osdCanvasDrawDirArrow(displayPort_t
*display
, displayCanvas_t
*canvas
, const osdDrawPoint_t
*p
, float degrees
)
128 const int topInset
= -2;
129 const int bottom
= -6;
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;
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);
150 displayCanvasFillStrokeTriangle(canvas
, 0, top
, width
, bottom
, -width
, bottom
);
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;
168 // Horizontal strokes
169 displayCanvasMoveToPoint(canvas
, -sz
, yc
);
170 displayCanvasStrokeLineToPoint(canvas
, -margin
, yc
);
171 displayCanvasMoveToPoint(canvas
, sz
, yc
);
172 displayCanvasStrokeLineToPoint(canvas
, margin
, yc
);
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
;
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
++) {
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);
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
);
270 float sx
= sin_approx(rollAngle
);
271 float sy
= cos_approx(-rollAngle
);
272 for (int ii
= pitchCenter
- 2; ii
<= pitchCenter
+ 2; ii
++) {
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
);
305 displayCanvasSetStrokeColor(canvas
, DISPLAY_CANVAS_COLOR_TRANSPARENT
);
306 displayCanvasSetLineOutlineColor(canvas
, DISPLAY_CANVAS_COLOR_TRANSPARENT
);
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
)
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
;
352 if (ahiY
+ ahiHeight
>= canvas
->height
) {
353 ahiY
= canvas
->height
- ahiHeight
- 1;
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
;
361 case OSD_AHI_STYLE_LINE
:
362 ahiStyle
= DISPLAY_WIDGET_AHI_STYLE_LINE
;
365 widgetAHIConfiguration_t config
= {
371 .options
= osdConfig()->ahi_bordered
? DISPLAY_WIDGET_AHI_OPTION_SHOW_CORNERS
: 0,
372 .crosshairMargin
= AHI_CROSSHAIR_MARGIN
,
375 if (!displayWidgetsConfigureAHI(&widgets
, instance
, &config
)) {
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
),
389 if (displayWidgetsDrawAHI(&widgets
, instance
, &data
)) {
390 if (++iterations
== 10) {
392 osdGridBufferClearPixelRect(canvas
, ahiX
, ahiY
, ahiWidth
, ahiHeight
);
400 void osdCanvasDrawArtificialHorizon(displayPort_t
*display
, displayCanvas_t
*canvas
, const osdDrawPoint_t
*p
, float pitchAngle
, float rollAngle
)
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
:
420 osdArtificialHorizonRect(canvas
, &x
, &y
, &w
, &h
);
421 displayCanvasClearRect(canvas
, x
, y
, w
, h
);
422 osdDrawArtificialHorizonShapes(canvas
, pitchAngle
, rollAngle
);
425 case OSD_AHI_STYLE_LINE
:
426 osdDrawArtificialHorizonLine(canvas
, prevPitchAngle
, prevRollAngle
, true);
427 osdDrawArtificialHorizonLine(canvas
, pitchAngle
, rollAngle
, false);
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
[] = {
444 SYM_HEADING_DIVIDED_LINE
,
448 SYM_HEADING_DIVIDED_LINE
,
452 SYM_HEADING_DIVIDED_LINE
,
456 SYM_HEADING_DIVIDED_LINE
,
460 SYM_HEADING_DIVIDED_LINE
,
464 SYM_HEADING_DIVIDED_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];
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
)
501 case OSD_SIDEBAR_SCROLL_NONE
:
503 case OSD_SIDEBAR_SCROLL_ALTITUDE
:
504 switch ((osd_unit_e
)osdConfig()->units
) {
509 case OSD_UNIT_IMPERIAL
:
510 return CENTIMETERS_TO_CENTIFEET(osdGetAltitude());
511 case OSD_UNIT_METRIC_MPH
:
513 case OSD_UNIT_METRIC
:
514 return osdGetAltitude();
517 case OSD_SIDEBAR_SCROLL_SPEED
:
520 int speed
= osdGetSpeedFromSelectedSource();
521 switch ((osd_unit_e
)osdConfig()->units
) {
524 case OSD_UNIT_METRIC_MPH
:
526 case OSD_UNIT_IMPERIAL
:
527 // cms/s to (mi/h) * 100
528 return speed
* 224 / 100;
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;
539 case OSD_SIDEBAR_SCROLL_HOME_DISTANCE
:
541 switch ((osd_unit_e
)osdConfig()->units
) {
546 case OSD_UNIT_IMPERIAL
:
547 return CENTIMETERS_TO_CENTIFEET(GPS_distanceToHome
* 100);
548 case OSD_UNIT_METRIC_MPH
:
550 case OSD_UNIT_METRIC
:
551 return GPS_distanceToHome
* 100;
559 static uint8_t osdCanvasSidebarGetOptions(int *width
, osd_sidebar_scroll_e scroll
)
562 case OSD_SIDEBAR_SCROLL_NONE
:
564 case OSD_SIDEBAR_SCROLL_ALTITUDE
:
566 case OSD_SIDEBAR_SCROLL_SPEED
:
568 case OSD_SIDEBAR_SCROLL_HOME_DISTANCE
:
569 *width
= OSD_CHAR_WIDTH
* 5; // 4 numbers + unit
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
583 case OSD_SIDEBAR_SCROLL_NONE
:
586 unit
->divided_symbol
= 0;
589 case OSD_SIDEBAR_SCROLL_ALTITUDE
:
590 switch ((osd_unit_e
)osdConfig()->units
) {
595 case OSD_UNIT_IMPERIAL
:
596 unit
->symbol
= SYM_ALT_FT
;
597 unit
->divisor
= FEET_PER_KILOFEET
;
598 unit
->divided_symbol
= SYM_ALT_KFT
;
601 case OSD_UNIT_METRIC_MPH
:
603 case OSD_UNIT_METRIC
:
604 unit
->symbol
= SYM_ALT_M
;
605 unit
->divisor
= METERS_PER_KILOMETER
;
606 unit
->divided_symbol
= SYM_ALT_KM
;
611 case OSD_SIDEBAR_SCROLL_SPEED
:
612 switch ((osd_unit_e
)osdConfig()->units
) {
615 case OSD_UNIT_METRIC_MPH
:
617 case OSD_UNIT_IMPERIAL
:
618 unit
->symbol
= SYM_MPH
;
620 unit
->divided_symbol
= 0;
624 unit
->symbol
= SYM_KT
;
626 unit
->divided_symbol
= 0;
629 case OSD_UNIT_METRIC
:
630 unit
->symbol
= SYM_KMH
;
632 unit
->divided_symbol
= 0;
637 case OSD_SIDEBAR_SCROLL_HOME_DISTANCE
:
638 switch ((osd_unit_e
)osdConfig()->units
) {
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;
648 unit
->symbol
= SYM_FT
;
649 unit
->divisor
= (int)FEET_PER_NAUTICALMILE
;
650 unit
->divided_symbol
= SYM_NM
;
651 *countsPerStep
= 300;
653 case OSD_UNIT_METRIC_MPH
:
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;
666 static bool osdCanvasDrawSidebar(uint32_t *configured
, displayWidgets_t
*widgets
,
667 displayCanvas_t
*canvas
,
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
);
674 uint32_t configuration
= scrollStep
<< 16 | (unsigned)osdConfig()->sidebar_horizontal_offset
<< 8 | scroll
<< 6 | osdConfig()->units
<< 4;
675 if (configuration
!= *configured
) {
677 uint8_t options
= osdCanvasSidebarGetOptions(&width
, scroll
);
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
= {
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
;
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
)) {
712 *configured
= configuration
;
715 int32_t data
= osdCanvasSidebarGetValue(scroll
);
716 return displayWidgetsDrawSidebar(widgets
, instance
, data
);
719 bool osdCanvasDrawSidebars(displayPort_t
*display
, displayCanvas_t
*canvas
)
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
) {
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
)) {
741 if (!osdCanvasDrawSidebar(&rightConfigured
, &widgets
, canvas
,
742 WIDGET_SIDEBAR_RIGHT_INSTANCE
,
743 osdConfig()->right_sidebar_scroll
,
744 osdConfig()->right_sidebar_scroll_step
)) {
747 nextRedraw
= now
+ SIDEBAR_REDRAW_INTERVAL_MS
;