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.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
);
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);
349 ahiY
+= osdConfig()->ahi_vertical_offset
;
354 if (ahiY
+ ahiHeight
>= canvas
->height
) {
355 ahiY
= canvas
->height
- ahiHeight
- 1;
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
;
363 case OSD_AHI_STYLE_LINE
:
364 ahiStyle
= DISPLAY_WIDGET_AHI_STYLE_LINE
;
367 widgetAHIConfiguration_t config
= {
373 .options
= osdConfig()->ahi_bordered
? DISPLAY_WIDGET_AHI_OPTION_SHOW_CORNERS
: 0,
374 .crosshairMargin
= AHI_CROSSHAIR_MARGIN
,
377 if (!displayWidgetsConfigureAHI(&widgets
, instance
, &config
)) {
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
),
391 if (displayWidgetsDrawAHI(&widgets
, instance
, &data
)) {
392 if (++iterations
== 10) {
394 osdGridBufferClearPixelRect(canvas
, ahiX
, ahiY
, ahiWidth
, ahiHeight
);
402 void osdCanvasDrawArtificialHorizon(displayPort_t
*display
, displayCanvas_t
*canvas
, const osdDrawPoint_t
*p
, float pitchAngle
, float rollAngle
)
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
:
422 osdArtificialHorizonRect(canvas
, &x
, &y
, &w
, &h
);
423 displayCanvasClearRect(canvas
, x
, y
, w
, h
);
424 osdDrawArtificialHorizonShapes(canvas
, pitchAngle
, rollAngle
);
427 case OSD_AHI_STYLE_LINE
:
428 osdDrawArtificialHorizonLine(canvas
, prevPitchAngle
, prevRollAngle
, true);
429 osdDrawArtificialHorizonLine(canvas
, pitchAngle
, rollAngle
, false);
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
[] = {
446 SYM_HEADING_DIVIDED_LINE
,
450 SYM_HEADING_DIVIDED_LINE
,
454 SYM_HEADING_DIVIDED_LINE
,
458 SYM_HEADING_DIVIDED_LINE
,
462 SYM_HEADING_DIVIDED_LINE
,
466 SYM_HEADING_DIVIDED_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];
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
)
503 case OSD_SIDEBAR_SCROLL_NONE
:
505 case OSD_SIDEBAR_SCROLL_ALTITUDE
:
506 switch ((osd_unit_e
)osdConfig()->units
) {
511 case OSD_UNIT_IMPERIAL
:
512 return CENTIMETERS_TO_CENTIFEET(osdGetAltitude());
513 case OSD_UNIT_METRIC_MPH
:
515 case OSD_UNIT_METRIC
:
516 return osdGetAltitude();
519 case OSD_SIDEBAR_SCROLL_SPEED
:
522 int16_t speed
= osdGetSpeedFromSelectedSource();
523 switch ((osd_unit_e
)osdConfig()->units
) {
526 case OSD_UNIT_METRIC_MPH
:
528 case OSD_UNIT_IMPERIAL
:
529 // cms/s to (mi/h) * 100
530 return speed
* 224 / 100;
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;
541 case OSD_SIDEBAR_SCROLL_HOME_DISTANCE
:
543 switch ((osd_unit_e
)osdConfig()->units
) {
548 case OSD_UNIT_IMPERIAL
:
549 return CENTIMETERS_TO_CENTIFEET(GPS_distanceToHome
* 100);
550 case OSD_UNIT_METRIC_MPH
:
552 case OSD_UNIT_METRIC
:
553 return GPS_distanceToHome
* 100;
561 static uint8_t osdCanvasSidebarGetOptions(int *width
, osd_sidebar_scroll_e scroll
)
564 case OSD_SIDEBAR_SCROLL_NONE
:
566 case OSD_SIDEBAR_SCROLL_ALTITUDE
:
568 case OSD_SIDEBAR_SCROLL_SPEED
:
570 case OSD_SIDEBAR_SCROLL_HOME_DISTANCE
:
571 *width
= OSD_CHAR_WIDTH
* 5; // 4 numbers + unit
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
585 case OSD_SIDEBAR_SCROLL_NONE
:
588 unit
->divided_symbol
= 0;
591 case OSD_SIDEBAR_SCROLL_ALTITUDE
:
592 switch ((osd_unit_e
)osdConfig()->units
) {
597 case OSD_UNIT_IMPERIAL
:
598 unit
->symbol
= SYM_ALT_FT
;
599 unit
->divisor
= FEET_PER_KILOFEET
;
600 unit
->divided_symbol
= SYM_ALT_KFT
;
603 case OSD_UNIT_METRIC_MPH
:
605 case OSD_UNIT_METRIC
:
606 unit
->symbol
= SYM_ALT_M
;
607 unit
->divisor
= METERS_PER_KILOMETER
;
608 unit
->divided_symbol
= SYM_ALT_KM
;
613 case OSD_SIDEBAR_SCROLL_SPEED
:
614 switch ((osd_unit_e
)osdConfig()->units
) {
617 case OSD_UNIT_METRIC_MPH
:
619 case OSD_UNIT_IMPERIAL
:
620 unit
->symbol
= SYM_MPH
;
622 unit
->divided_symbol
= 0;
626 unit
->symbol
= SYM_KT
;
628 unit
->divided_symbol
= 0;
631 case OSD_UNIT_METRIC
:
632 unit
->symbol
= SYM_KMH
;
634 unit
->divided_symbol
= 0;
639 case OSD_SIDEBAR_SCROLL_HOME_DISTANCE
:
640 switch ((osd_unit_e
)osdConfig()->units
) {
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;
650 unit
->symbol
= SYM_FT
;
651 unit
->divisor
= (int)FEET_PER_NAUTICALMILE
;
652 unit
->divided_symbol
= SYM_NM
;
653 *countsPerStep
= 300;
655 case OSD_UNIT_METRIC_MPH
:
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;
668 static bool osdCanvasDrawSidebar(uint32_t *configured
, displayWidgets_t
*widgets
,
669 displayCanvas_t
*canvas
,
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
);
676 uint32_t configuration
= scrollStep
<< 16 | (unsigned)osdConfig()->sidebar_horizontal_offset
<< 8 | scroll
<< 6 | osdConfig()->units
<< 4;
677 if (configuration
!= *configured
) {
679 uint8_t options
= osdCanvasSidebarGetOptions(&width
, scroll
);
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
= {
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
;
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
)) {
714 *configured
= configuration
;
717 int32_t data
= osdCanvasSidebarGetValue(scroll
);
718 return displayWidgetsDrawSidebar(widgets
, instance
, data
);
721 bool osdCanvasDrawSidebars(displayPort_t
*display
, displayCanvas_t
*canvas
)
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
) {
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
)) {
743 if (!osdCanvasDrawSidebar(&rightConfigured
, &widgets
, canvas
,
744 WIDGET_SIDEBAR_RIGHT_INSTANCE
,
745 osdConfig()->right_sidebar_scroll
,
746 osdConfig()->right_sidebar_scroll_step
)) {
749 nextRedraw
= now
+ SIDEBAR_REDRAW_INTERVAL_MS
;