4 XCSoar Glide Computer - http://www.xcsoar.org/
5 Copyright (C) 2000-2013 The XCSoar Project
6 A detailed list of copyright holders can be found in the file "AUTHORS".
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 #include "Gauge/GaugeVario.hpp"
25 #include "Look/VarioLook.hpp"
26 #include "Look/UnitsLook.hpp"
27 #include "Screen/Canvas.hpp"
28 #include "Screen/UnitSymbol.hpp"
29 #include "Screen/Layout.hpp"
30 #include "Screen/FastPixelRotation.hpp"
31 #include "LogFile.hpp"
32 #include "Units/Units.hpp"
33 #include "Util/Clamp.hpp"
43 #define DELTA_V_STEP fixed(4)
44 #define DELTA_V_LIMIT fixed(16)
45 #define TEXT_BUG _T("Bug")
46 #define TEXT_BALLAST _T("Bal")
48 GaugeVario::GaugeVario(const FullBlackboard
&_blackboard
,
49 ContainerWindow
&parent
, const VarioLook
&_look
,
50 const UnitsLook
&_units_look
,
51 PixelRect rc
, const WindowStyle style
)
52 :blackboard(_blackboard
), look(_look
), units_look(_units_look
),
53 nlength0(Layout::Scale(15)),
54 nlength1(Layout::Scale(6)),
55 nwidth(Layout::Scale(4)),
56 nline(Layout::Scale(8)),
57 dirty(true), layout_initialised(false), needle_initialised(false),
58 ballast_initialised(false), bugs_initialised(false)
60 value_top
.initialised
= false;
61 value_middle
.initialised
= false;
62 value_bottom
.initialised
= false;
63 label_top
.initialised
= false;
64 label_middle
.initialised
= false;
65 label_bottom
.initialised
= false;
67 Create(parent
, rc
, style
);
71 GaugeVario::OnPaintBuffer(Canvas
&canvas
)
73 const PixelRect rc
= GetClientRect();
74 const UPixelScalar width
= rc
.right
- rc
.left
;
75 const UPixelScalar height
= rc
.bottom
- rc
.top
;
77 if (!IsPersistent() || !layout_initialised
) {
78 UPixelScalar value_height
= 4 + look
.value_font
->GetCapitalHeight()
79 + look
.text_font
->GetCapitalHeight();
81 middle_position
.y
= yoffset
- value_height
/ 2;
82 middle_position
.x
= rc
.right
;
83 top_position
.y
= middle_position
.y
- value_height
;
84 top_position
.x
= rc
.right
;
85 bottom_position
.y
= middle_position
.y
+ value_height
;
86 bottom_position
.x
= rc
.right
;
88 canvas
.Stretch(rc
.left
, rc
.top
, width
, height
,
89 look
.background_bitmap
,
90 look
.background_x
, 0, 58, 120);
92 layout_initialised
= true;
95 if (Settings().show_average
) {
96 // JMW averager now displays netto average if not circling
97 if (!Calculated().circling
) {
98 RenderValue(canvas
, top_position
.x
, top_position
.y
, &value_top
, &label_top
,
99 Units::ToUserVSpeed(Calculated().netto_average
),
102 RenderValue(canvas
, top_position
.x
, top_position
.y
,
103 &value_top
, &label_top
,
104 Units::ToUserVSpeed(Calculated().average
), _T("Avg"));
108 if (Settings().show_mc
) {
109 fixed mc
= Units::ToUserVSpeed(GetGlidePolar().GetMC());
110 RenderValue(canvas
, bottom_position
.x
, bottom_position
.y
,
111 &value_bottom
, &label_bottom
,
113 GetComputerSettings().task
.auto_mc
? _T("Auto MC") : _T("MC"));
116 if (Settings().show_speed_to_fly
)
117 RenderSpeedToFly(canvas
, rc
.right
- 11, (rc
.top
+ rc
.bottom
) / 2);
121 if (Settings().show_ballast
)
122 RenderBallast(canvas
);
124 if (Settings().show_bugs
)
128 int ival
, sval
, ival_av
= 0;
129 static int vval_last
= 0;
130 static int sval_last
= 0;
131 static int ival_last
= 0;
133 fixed vval
= Basic().brutto_vario
;
134 ival
= ValueToNeedlePos(fixed(vval
));
135 sval
= ValueToNeedlePos(Calculated().sink_rate
);
136 if (Settings().show_average_needle
) {
137 if (!Calculated().circling
)
138 ival_av
= ValueToNeedlePos(Calculated().netto_average
);
140 ival_av
= ValueToNeedlePos(Calculated().average
);
145 if (Settings().show_average_needle
) {
146 if (!IsPersistent() || ival_av
!= ival_last
)
147 RenderNeedle(canvas
, ival_last
, true, true);
152 if (!IsPersistent() || (sval
!= sval_last
) || (ival
!= vval_last
))
153 RenderVarioLine(canvas
, vval_last
, sval_last
, true);
157 if (!IsPersistent() || ival
!= vval_last
)
158 RenderNeedle(canvas
, vval_last
, false, true);
163 RenderVarioLine(canvas
, ival
, sval
, false);
164 if (Settings().show_average_needle
)
165 RenderNeedle(canvas
, ival_av
, true, false);
167 RenderNeedle(canvas
, ival
, false, false);
169 if (Settings().show_gross
) {
170 fixed vvaldisplay
= Clamp(Units::ToUserVSpeed(vval
),
171 fixed(-99.9), fixed(99.9));
173 RenderValue(canvas
, middle_position
.x
, middle_position
.y
,
174 &value_middle
, &label_middle
,
184 TransformRotatedPoint(RasterPoint pt
, PixelScalar xoffset
, PixelScalar yoffset
)
186 return { pt
.x
+ xoffset
, (pt
.y
* 112 / 100) + yoffset
+ 1 };
190 GaugeVario::MakePolygon(const int i
)
192 RasterPoint
*bit
= getPolygon(i
);
193 RasterPoint
*bline
= &lines
[i
+ gmax
];
195 const FastPixelRotation
r(Angle::Degrees(i
));
197 bit
[0] = TransformRotatedPoint(r
.Rotate(-xoffset
+ nlength0
, nwidth
),
199 bit
[1] = TransformRotatedPoint(r
.Rotate(-xoffset
+ nlength1
, 0),
201 bit
[2] = TransformRotatedPoint(r
.Rotate(-xoffset
+ nlength0
, -nwidth
),
204 *bline
= TransformRotatedPoint(r
.Rotate(-xoffset
+ nline
, 0),
209 GaugeVario::getPolygon(int i
)
211 return polys
+ (i
+ gmax
) * 3;
215 GaugeVario::MakeAllPolygons()
218 for (int i
= -gmax
; i
<= gmax
; i
++)
223 GaugeVario::RenderClimb(Canvas
&canvas
)
225 const PixelRect rc
= GetClientRect();
226 PixelScalar x
= rc
.right
- Layout::Scale(14);
227 PixelScalar y
= rc
.bottom
- Layout::Scale(24);
232 if (Basic().switch_state
.flight_mode
== SwitchState::FlightMode::CIRCLING
)
233 canvas
.ScaleCopy(x
, y
, look
.climb_bitmap
, 12, 0, 12, 12);
234 else if (IsPersistent())
235 canvas
.DrawFilledRectangle(x
, y
, x
+ Layout::Scale(12), y
+ Layout::Scale(12),
236 look
.background_color
);
240 GaugeVario::RenderZero(Canvas
&canvas
)
243 canvas
.SelectWhitePen();
245 canvas
.SelectBlackPen();
247 canvas
.DrawLine(0, yoffset
, Layout::Scale(17), yoffset
);
248 canvas
.DrawLine(0, yoffset
+ 1, Layout::Scale(17), yoffset
+ 1);
252 GaugeVario::ValueToNeedlePos(fixed Value
)
254 static fixed degrees_per_unit
= fixed(GAUGEVARIOSWEEP
) / GAUGEVARIORANGE
;
257 if (!needle_initialised
){
259 needle_initialised
= true;
261 i
= iround(Value
* degrees_per_unit
);
262 i
= Clamp(i
, -int(gmax
), int(gmax
));
267 GaugeVario::RenderVarioLine(Canvas
&canvas
, int i
, int sink
, bool clear
)
274 ? look
.thick_background_pen
275 : (i
> sink
? look
.thick_lift_pen
: look
.thick_sink_pen
));
278 canvas
.DrawPolyline(lines
+ gmax
+ sink
, i
- sink
);
280 canvas
.DrawPolyline(lines
+ gmax
+ i
, sink
- i
);
283 canvas
.SelectNullPen();
285 // clear up naked (sink) edge of polygon, this gives it a nice
288 canvas
.SelectBlackBrush();
290 canvas
.SelectWhiteBrush();
292 canvas
.DrawTriangleFan(getPolygon(sink
), 3);
297 GaugeVario::RenderNeedle(Canvas
&canvas
, int i
, bool average
, bool clear
)
301 canvas
.SelectNullPen();
304 if (clear
^ look
.inverse
) {
305 canvas
.SelectWhiteBrush();
307 canvas
.SelectBlackBrush();
311 canvas
.DrawPolyline(getPolygon(i
), 3);
313 canvas
.DrawTriangleFan(getPolygon(i
), 3);
316 // TODO code: Optimise vario rendering, this is slow
318 GaugeVario::RenderValue(Canvas
&canvas
, PixelScalar x
, PixelScalar y
,
319 DrawInfo
*value_info
, DrawInfo
*label_info
,
320 fixed value
, const TCHAR
*label
)
325 value
= (double)iround(value
* 10) / 10; // prevent the -0.0 case
328 if (!value_info
->initialised
) {
330 value_info
->rc
.right
= x
- Layout::Scale(5);
331 value_info
->rc
.top
= y
+ Layout::Scale(3)
332 + look
.text_font
->GetCapitalHeight();
334 value_info
->rc
.left
= value_info
->rc
.right
;
335 // update back rect with max label size
336 value_info
->rc
.bottom
= value_info
->rc
.top
+ look
.value_font
->GetCapitalHeight();
338 value_info
->text_position
.x
= value_info
->rc
.left
;
339 value_info
->text_position
.y
= value_info
->rc
.top
340 + look
.value_font
->GetCapitalHeight()
341 - look
.value_font
->GetAscentHeight();
343 value_info
->last_value
= fixed(-9999);
344 value_info
->last_text
[0] = '\0';
345 value_info
->last_unit
= Unit::UNDEFINED
;
346 value_info
->initialised
= true;
349 if (!label_info
->initialised
) {
351 label_info
->rc
.right
= x
;
352 label_info
->rc
.top
= y
+ Layout::Scale(1);
354 label_info
->rc
.left
= label_info
->rc
.right
;
355 // update back rect with max label size
356 label_info
->rc
.bottom
= label_info
->rc
.top
357 + look
.text_font
->GetCapitalHeight();
359 label_info
->text_position
.x
= label_info
->rc
.left
;
360 label_info
->text_position
.y
= label_info
->rc
.top
361 + look
.text_font
->GetCapitalHeight()
362 - look
.text_font
->GetAscentHeight();
364 label_info
->last_value
= fixed(-9999);
365 label_info
->last_text
[0] = '\0';
366 label_info
->initialised
= true;
369 canvas
.SetBackgroundTransparent();
371 if (!IsPersistent() || (dirty
&& _tcscmp(label_info
->last_text
, label
) != 0)) {
372 canvas
.SetTextColor(look
.dimmed_text_color
);
373 canvas
.Select(*look
.text_font
);
374 tsize
= canvas
.CalcTextSize(label
);
375 label_info
->text_position
.x
= label_info
->rc
.right
- tsize
.cx
;
377 if (IsPersistent()) {
378 canvas
.SetBackgroundColor(look
.background_color
);
379 canvas
.DrawOpaqueText(label_info
->text_position
.x
, label_info
->text_position
.y
,
380 label_info
->rc
, label
);
381 label_info
->rc
.left
= label_info
->text_position
.x
;
382 _tcscpy(label_info
->last_text
, label
);
384 canvas
.DrawText(label_info
->text_position
.x
, label_info
->text_position
.y
,
389 if (!IsPersistent() || (dirty
&& value_info
->last_value
!= value
)) {
391 canvas
.SetBackgroundColor(look
.background_color
);
392 canvas
.SetTextColor(look
.text_color
);
393 _stprintf(buffer
, _T("%.1f"), (double)value
);
394 canvas
.Select(*look
.value_font
);
395 tsize
= canvas
.CalcTextSize(buffer
);
396 value_info
->text_position
.x
= value_info
->rc
.right
- tsize
.cx
;
398 if (IsPersistent()) {
399 canvas
.DrawOpaqueText(value_info
->text_position
.x
,
400 value_info
->text_position
.y
,
401 value_info
->rc
, buffer
);
403 value_info
->rc
.left
= value_info
->text_position
.x
;
404 value_info
->last_value
= value
;
406 canvas
.DrawText(value_info
->text_position
.x
, value_info
->text_position
.y
,
411 if (!IsPersistent() ||
412 value_info
->last_unit
!= Units::current
.vertical_speed_unit
) {
413 value_info
->last_unit
= Units::current
.vertical_speed_unit
;
414 const UnitSymbol
*unit_symbol
= units_look
.GetSymbol(value_info
->last_unit
);
415 unit_symbol
->Draw(canvas
, x
- Layout::Scale(5), value_info
->rc
.top
,
417 ? UnitSymbol::INVERSE_GRAY
423 GaugeVario::RenderSpeedToFly(Canvas
&canvas
, PixelScalar x
, PixelScalar y
)
425 if (!Basic().airspeed_available
||
426 !Basic().total_energy_vario_available
)
429 static fixed last_v_diff
;
432 const UPixelScalar arrow_y_size
= Layout::Scale(3);
433 const UPixelScalar arrow_x_size
= Layout::Scale(7);
435 const PixelRect rc
= GetClientRect();
437 PixelScalar nary
= NARROWS
* arrow_y_size
;
438 PixelScalar ytop
= rc
.top
+ YOFFSET
+ nary
; // JMW
439 PixelScalar ybottom
= rc
.bottom
- YOFFSET
- nary
- Layout::FastScale(1);
441 ytop
+= Layout::Scale(14);
442 ybottom
-= Layout::Scale(14);
444 x
= rc
.right
- 2 * arrow_x_size
;
446 // only draw speed command if flying and vario is not circling
447 if ((Calculated().flight
.flying
)
448 && (!Basic().gps
.simulator
|| !Calculated().circling
)) {
449 v_diff
= Calculated().V_stf
- Basic().indicated_airspeed
;
450 v_diff
= Clamp(v_diff
, -DELTA_V_LIMIT
, DELTA_V_LIMIT
); // limit it
451 v_diff
= iround(v_diff
/DELTA_V_STEP
) * DELTA_V_STEP
;
455 if (!IsPersistent() || last_v_diff
!= v_diff
|| dirty
) {
456 last_v_diff
= v_diff
;
458 if (IsPersistent()) {
460 canvas
.DrawFilledRectangle(x
, ybottom
+ YOFFSET
,
461 x
+ arrow_x_size
* 2 + 1,
462 ybottom
+ YOFFSET
+ nary
+ arrow_y_size
+
463 Layout::FastScale(2),
464 look
.background_color
);
467 canvas
.DrawFilledRectangle(x
, ytop
- YOFFSET
+ 1,
468 x
+ arrow_x_size
* 2 +1,
469 ytop
- YOFFSET
- nary
+ 1 - arrow_y_size
-
470 Layout::FastScale(2),
471 look
.background_color
);
476 canvas
.SelectNullPen();
479 if (positive(v_diff
)) {
481 canvas
.Select(look
.sink_brush
);
483 canvas
.Select(look
.lift_brush
);
487 canvas
.SelectWhiteBrush();
489 canvas
.SelectBlackBrush();
492 if (positive(v_diff
)) {
497 while (positive(v_diff
)) {
498 if (v_diff
> DELTA_V_STEP
) {
499 canvas
.Rectangle(x
, y
,
500 x
+ arrow_x_size
* 2 + 1, y
+ arrow_y_size
- 1);
502 RasterPoint arrow
[3];
505 arrow
[1].x
= x
+ arrow_x_size
;
506 arrow
[1].y
= y
+ arrow_y_size
- 1;
507 arrow
[2].x
= x
+ 2 * arrow_x_size
;
509 canvas
.DrawTriangleFan(arrow
, 3);
511 v_diff
-= DELTA_V_STEP
;
514 } else if (negative(v_diff
)) {
519 while (negative(v_diff
)) {
520 if (v_diff
< -DELTA_V_STEP
) {
521 canvas
.Rectangle(x
, y
+ 1,
522 x
+ arrow_x_size
* 2 + 1, y
- arrow_y_size
+ 2);
524 RasterPoint arrow
[3];
527 arrow
[1].x
= x
+ arrow_x_size
;
528 arrow
[1].y
= y
- arrow_y_size
+ 1;
529 arrow
[2].x
= x
+ 2 * arrow_x_size
;
531 canvas
.DrawTriangleFan(arrow
, 3);
533 v_diff
+= DELTA_V_STEP
;
541 GaugeVario::RenderBallast(Canvas
&canvas
)
543 static unsigned last_ballast
= -1;
544 static PixelRect label_rect
= {-1,-1,-1,-1};
545 static PixelRect value_rect
= {-1,-1,-1,-1};
546 static RasterPoint label_pos
= {-1,-1};
547 static RasterPoint value_pos
= {-1,-1};
549 if (!ballast_initialised
) { // ontime init, origin and background rect
550 const PixelRect rc
= GetClientRect();
554 // position of ballast label
556 label_pos
.y
= rc
.top
+ 2
557 + look
.text_font
->GetCapitalHeight() * 2
558 - look
.text_font
->GetAscentHeight();
560 // position of ballast value
562 value_pos
.y
= rc
.top
+ 1
563 + look
.text_font
->GetCapitalHeight()
564 - look
.text_font
->GetAscentHeight();
566 // set upper left corner
567 label_rect
.left
= label_pos
.x
;
568 label_rect
.top
= label_pos
.y
569 + look
.text_font
->GetAscentHeight()
570 - look
.text_font
->GetCapitalHeight();
572 // set upper left corner
573 value_rect
.left
= value_pos
.x
;
574 value_rect
.top
= value_pos
.y
575 + look
.text_font
->GetAscentHeight()
576 - look
.text_font
->GetCapitalHeight();
578 // get max label size
579 canvas
.Select(*look
.text_font
);
580 tSize
= canvas
.CalcTextSize(TEXT_BALLAST
);
582 // update back rect with max label size
583 label_rect
.right
= label_rect
.left
+ tSize
.cx
;
584 label_rect
.bottom
= label_rect
.top
+
585 look
.text_font
->GetCapitalHeight();
587 // get max value size
588 tSize
= canvas
.CalcTextSize(_T("100%"));
590 value_rect
.right
= value_rect
.left
+ tSize
.cx
;
591 // update back rect with max label size
592 value_rect
.bottom
= value_rect
.top
+
593 look
.text_font
->GetCapitalHeight();
595 ballast_initialised
= true;
598 unsigned ballast
= uround(GetGlidePolar().GetBugs() * 100);
600 if (!IsPersistent() || ballast
!= last_ballast
) {
601 // ballast hase been changed
603 canvas
.Select(*look
.text_font
);
606 canvas
.SetBackgroundColor(look
.background_color
);
608 canvas
.SetBackgroundTransparent();
610 if (IsPersistent() || last_ballast
== 0 || ballast
== 0) {
611 // new ballast is 0, hide label
613 canvas
.SetTextColor(look
.dimmed_text_color
);
614 // ols ballast was 0, show label
616 canvas
.DrawOpaqueText(label_pos
.x
, label_pos
.y
, label_rect
, TEXT_BALLAST
);
618 canvas
.DrawText(label_pos
.x
, label_pos
.y
, TEXT_BALLAST
);
619 } else if (IsPersistent())
620 canvas
.DrawFilledRectangle(label_rect
, look
.background_color
);
623 // new ballast 0, hide value
626 _stprintf(buffer
, _T("%u%%"), ballast
);
627 canvas
.SetTextColor(look
.text_color
);
630 canvas
.DrawOpaqueText(value_pos
.x
, value_pos
.y
, value_rect
, buffer
);
632 canvas
.DrawText(value_pos
.x
, value_pos
.y
, buffer
);
633 } else if (IsPersistent())
634 canvas
.DrawFilledRectangle(value_rect
, look
.background_color
);
637 last_ballast
= ballast
;
642 GaugeVario::RenderBugs(Canvas
&canvas
)
644 static int last_bugs
= -1;
645 static PixelRect label_rect
= {-1,-1,-1,-1};
646 static PixelRect value_rect
= {-1,-1,-1,-1};
647 static RasterPoint label_pos
= {-1,-1};
648 static RasterPoint value_pos
= {-1,-1};
650 if (!bugs_initialised
) {
651 const PixelRect rc
= GetClientRect();
655 label_pos
.y
= rc
.bottom
- 2
656 - look
.text_font
->GetCapitalHeight()
657 - look
.text_font
->GetAscentHeight();
660 value_pos
.y
= rc
.bottom
- 1
661 - look
.text_font
->GetAscentHeight();
663 label_rect
.left
= label_pos
.x
;
664 label_rect
.top
= label_pos
.y
665 + look
.text_font
->GetAscentHeight()
666 - look
.text_font
->GetCapitalHeight();
667 value_rect
.left
= value_pos
.x
;
668 value_rect
.top
= value_pos
.y
669 + look
.text_font
->GetAscentHeight()
670 - look
.text_font
->GetCapitalHeight();
672 canvas
.Select(*look
.text_font
);
673 tSize
= canvas
.CalcTextSize(TEXT_BUG
);
675 label_rect
.right
= label_rect
.left
+ tSize
.cx
;
676 label_rect
.bottom
= label_rect
.top
677 + look
.text_font
->GetCapitalHeight()
678 + look
.text_font
->GetHeight()
679 - look
.text_font
->GetAscentHeight();
681 tSize
= canvas
.CalcTextSize(_T("100%"));
683 value_rect
.right
= value_rect
.left
+ tSize
.cx
;
684 value_rect
.bottom
= value_rect
.top
+
685 look
.text_font
->GetCapitalHeight();
687 bugs_initialised
= true;
690 int bugs
= iround((fixed(1) - GetComputerSettings().polar
.bugs
) * 100);
691 if (!IsPersistent() || bugs
!= last_bugs
) {
693 canvas
.Select(*look
.text_font
);
696 canvas
.SetBackgroundColor(look
.background_color
);
698 canvas
.SetBackgroundTransparent();
700 if (IsPersistent() || last_bugs
< 1 || bugs
< 1) {
702 canvas
.SetTextColor(look
.dimmed_text_color
);
704 canvas
.DrawOpaqueText(label_pos
.x
, label_pos
.y
, label_rect
, TEXT_BUG
);
706 canvas
.DrawText(label_pos
.x
, label_pos
.y
, TEXT_BUG
);
707 } else if (IsPersistent())
708 canvas
.DrawFilledRectangle(label_rect
, look
.background_color
);
713 _stprintf(buffer
, _T("%d%%"), bugs
);
714 canvas
.SetTextColor(look
.text_color
);
716 canvas
.DrawOpaqueText(value_pos
.x
, value_pos
.y
, value_rect
, buffer
);
718 canvas
.DrawText(value_pos
.x
, value_pos
.y
, buffer
);
719 } else if (IsPersistent())
720 canvas
.DrawFilledRectangle(value_rect
, look
.background_color
);
728 GaugeVario::OnResize(PixelSize new_size
)
730 AntiFlickerWindow::OnResize(new_size
);
732 /* trigger reinitialisation */
733 xoffset
= new_size
.cx
;
734 yoffset
= new_size
.cy
/ 2;
735 layout_initialised
= false;
736 needle_initialised
= false;
737 ballast_initialised
= false;
738 bugs_initialised
= false;
740 value_top
.initialised
= false;
741 value_middle
.initialised
= false;
742 value_bottom
.initialised
= false;
743 label_top
.initialised
= false;
744 label_middle
.initialised
= false;
745 label_bottom
.initialised
= false;