示例#1
1
void
ThermalAssistantWindow::PaintRadarBackground(Canvas &canvas, fixed max_lift) const
{
  canvas.Clear(look.background_color);
  canvas.SelectHollowBrush();

  canvas.Select(look.inner_circle_pen);
  canvas.DrawCircle(mid.x, mid.y, radius / 2);
  canvas.Select(look.outer_circle_pen);
  canvas.DrawCircle(mid.x, mid.y, radius);

  if (small)
    return;

  canvas.SetTextColor(COLOR_BLACK);
  canvas.Select(look.circle_label_font);
  canvas.SetBackgroundColor(look.background_color);
  canvas.SetBackgroundOpaque();

  TCHAR lift_string[10];
  FormatUserVerticalSpeed(max_lift, lift_string, ARRAY_SIZE(lift_string));
  PixelSize s = canvas.CalcTextSize(lift_string);
  canvas.text(mid.x - s.cx / 2,
              mid.y + radius - s.cy * 0.75, lift_string);

  FormatUserVerticalSpeed(fixed_zero, lift_string, ARRAY_SIZE(lift_string));
  s = canvas.CalcTextSize(lift_string);
  canvas.text(mid.x - s.cx / 2,
              mid.y + radius / 2 - s.cy * 0.75, lift_string);

  canvas.SetBackgroundTransparent();
}
示例#2
0
void
MacCreadyEditPanel::Prepare(ContainerWindow &parent, const PixelRect &rc)
{
  LoadWindow(call_back_table, parent, _T("IDR_XML_INFOBOXMACCREADYEDIT"));

  TCHAR caption[16];

  WndButton *button = (WndButton *)form.FindByName(_T("cmdPlusBig"));
  assert(button != NULL);
  FormatUserVerticalSpeed(
      Units::ToSysVSpeed(GetUserVerticalSpeedStep() * 5), caption, false);
  button->SetCaption(caption);

  button = (WndButton *)form.FindByName(_T("cmdPlusSmall"));
  assert(button != NULL);
  FormatUserVerticalSpeed(
      Units::ToSysVSpeed(GetUserVerticalSpeedStep()), caption, false);
  button->SetCaption(caption);

  button = (WndButton *)form.FindByName(_T("cmdMinusBig"));
  assert(button != NULL);
  FormatUserVerticalSpeed(
      Units::ToSysVSpeed(-GetUserVerticalSpeedStep() * 5), caption, false);
  button->SetCaption(caption);

  button = (WndButton *)form.FindByName(_T("cmdMinusSmall"));
  assert(button != NULL);
  FormatUserVerticalSpeed(
      Units::ToSysVSpeed(-GetUserVerticalSpeedStep()), caption, false);
  button->SetCaption(caption);
}
示例#3
0
void
MapItemListRenderer::Draw(Canvas &canvas, const PixelRect rc,
                          const TrafficMapItem &item,
                          const DialogLook &dialog_look,
                          const TrafficLook &traffic_look)
{
  const PixelScalar line_height = rc.bottom - rc.top;
  const FlarmTraffic traffic = item.traffic;

  // Now render the text information
  const Font &name_font = *dialog_look.list.font;
  const Font &small_font = *dialog_look.small_font;
  PixelScalar left = rc.left + line_height + Layout::FastScale(2);

  const FlarmRecord *record = FlarmDetails::LookupRecord(item.traffic.id);

  StaticString<256> title_string;
  if (record && !StringIsEmpty(record->pilot))
    title_string = record->pilot.c_str();
  else
    title_string = _("FLARM Traffic");

  // Append name to the title, if it exists
  if (traffic.HasName()) {
    title_string.append(_T(", "));
    title_string.append(traffic.name);
  }

  canvas.Select(name_font);
  canvas.text_clipped(left, rc.top + Layout::FastScale(2), rc, title_string);

  StaticString<256> info_string;
  if (record && !StringIsEmpty(record->plane_type))
    info_string = record->plane_type;
  else
    info_string = FlarmTraffic::GetTypeString(item.traffic.type);

  // Generate the line of info about the target, if it's available
  if (traffic.altitude_available) {
    TCHAR tmp[15];
    FormatUserAltitude(traffic.altitude, tmp, 15);
    info_string.AppendFormat(_T(", %s: %s"), _("Altitude"), tmp);
  }
  if (traffic.climb_rate_avg30s_available) {
    TCHAR tmp[15];
    FormatUserVerticalSpeed(traffic.climb_rate_avg30s, tmp, 15);
    info_string.AppendFormat(_T(", %s: %s"), _("Vario"), tmp);
  }
  canvas.Select(small_font);
  canvas.text_clipped(left,
                      rc.top + name_font.GetHeight() + Layout::FastScale(4),
                      rc, info_string);

  RasterPoint pt = { (PixelScalar)(rc.left + line_height / 2),
                     (PixelScalar)(rc.top + line_height / 2) };

  // Render the representation of the traffic icon
  TrafficRenderer::Draw(canvas, traffic_look, traffic, traffic.track,
                        item.color, pt);
}
示例#4
0
static void
Draw(Canvas &canvas, PixelRect rc,
     const ThermalMapItem &item,
     RoughTimeDelta utc_offset,
     const TwoTextRowsRenderer &row_renderer,
     const MapLook &look)
{
  const unsigned line_height = rc.GetHeight();
  const unsigned text_padding = Layout::GetTextPadding();

  const ThermalSource &thermal = item.thermal;

  const PixelPoint pt(rc.left + line_height / 2,
                      rc.top + line_height / 2);

  look.thermal_source_icon.Draw(canvas, pt);

  rc.left += line_height + text_padding;

  row_renderer.DrawFirstRow(canvas, rc, _("Thermal"));

  StaticString<256> buffer;
  TCHAR lift_buffer[32];
  FormatUserVerticalSpeed(thermal.lift_rate, lift_buffer, 32);

  int timespan = BrokenDateTime::NowUTC().GetSecondOfDay() - (int)thermal.time;
  if (timespan < 0)
    timespan += 24 * 60 * 60;

  buffer.Format(_T("%s: %s - left %s ago (%s)"),
                _("Avg. lift"), lift_buffer,
                FormatTimespanSmart(timespan).c_str(),
                FormatLocalTimeHHMM((int)thermal.time, utc_offset).c_str());
  row_renderer.DrawSecondRow(canvas, rc, buffer);
}
示例#5
0
static void
SetVSpeed(InfoBoxData &data, fixed value)
{
  TCHAR buffer[32];
  FormatUserVerticalSpeed(value, buffer, false);
  data.SetValue(buffer[0] == _T('+') ? buffer + 1 : buffer);
  data.SetValueUnit(Units::current.vertical_speed_unit);
}
示例#6
0
/**
 * Updates all the dialogs fields, that are changing frequently.
 * e.g. climb speed, distance, height
 */
void
FlarmTrafficDetailsWidget::UpdateChanging(const MoreData &basic)
{
  TCHAR tmp[40];
  const TCHAR *value;

  const FlarmTraffic* target =
    basic.flarm.traffic.FindTraffic(target_id);

  bool target_ok = target && target->IsDefined();

  // Fill distance/direction field
  if (target_ok) {
    FormatUserDistanceSmart(target->distance, tmp, 20, fixed(1000));
    TCHAR *p = tmp + _tcslen(tmp);
    *p++ = _T(' ');
    FormatAngleDelta(p, 20, target->Bearing() - basic.track);
    value = tmp;
  } else
    value = _T("--");

  SetText(DISTANCE, value);

  // Fill altitude field
  if (target_ok) {
    TCHAR *p = tmp;
    if (target->altitude_available) {
      FormatUserAltitude(target->altitude, p, 20);
      p += _tcslen(p);
      *p++ = _T(' ');
    }

    Angle dir = Angle::FromXY(target->distance, target->relative_altitude);
    FormatVerticalAngleDelta(p, 20, dir);

    value = tmp;
  } else
    value = _T("--");

  SetText(ALTITUDE, value);

  // Fill climb speed field
  if (target_ok && target->climb_rate_avg30s_available) {
    FormatUserVerticalSpeed(target->climb_rate_avg30s, tmp, 20);
    value = tmp;
  } else
    value = _T("--");

  SetText(VARIO, value);
}
示例#7
0
void
InfoBoxContentSpark::SetVSpeedComment(InfoBoxData &data,
                                      const TraceVariableHistory &var)
{
  if (var.empty())
    return;

  TCHAR sTmp[32];
  FormatUserVerticalSpeed(var.last(), sTmp,
                          ARRAY_SIZE(sTmp));
  data.SetComment(sTmp);

  data.SetCustom();
}
示例#8
0
/**
 * Updates all the dialogs fields, that are changing frequently.
 * e.g. climb speed, distance, height
 */
static void
UpdateChanging()
{
  TCHAR tmp[20];
  const FlarmTraffic* target =
    XCSoarInterface::Basic().flarm.traffic.FindTraffic(target_id);

  bool target_ok = target && target->IsDefined();

  // Fill distance field
  if (target_ok)
    FormatUserDistanceSmart(target->distance, tmp, 20, fixed(1000));
  else
    _tcscpy(tmp, _T("--"));
  SetFormValue(*wf, _T("prpDistance"), tmp);

  // Fill horizontal direction field
  if (target_ok)
    FormatAngleDelta(tmp, ARRAY_SIZE(tmp),
                     target->Bearing() - CommonInterface::Basic().track);
  else
    _tcscpy(tmp, _T("--"));
  SetFormValue(*wf, _T("prpDirectionH"), tmp);

  // Fill altitude field
  if (target_ok && target->altitude_available)
    FormatUserAltitude(target->altitude, tmp, 20);
  else
    _tcscpy(tmp, _T("--"));
  SetFormValue(*wf, _T("prpAltitude"), tmp);

  // Fill vertical direction field
  if (target_ok) {
    Angle dir = Angle::Radians((fixed)atan2(target->relative_altitude,
                                            target->distance)).AsDelta();
    FormatVerticalAngleDelta(tmp, ARRAY_SIZE(tmp), dir);
  } else
    _tcscpy(tmp, _T("--"));
  SetFormValue(*wf, _T("prpDirectionV"), tmp);

  // Fill climb speed field
  if (target_ok && target->climb_rate_avg30s_available)
    FormatUserVerticalSpeed(target->climb_rate_avg30s, tmp, 20);
  else
    _tcscpy(tmp, _T("--"));
  SetFormValue(*wf, _T("prpVSpeed"), tmp);
}
示例#9
0
void
MapItemListRenderer::Draw(Canvas &canvas, const PixelRect rc,
                          const ThermalMapItem &item,
                          const DialogLook &dialog_look,
                          const MapLook &look)
{
  const PixelScalar line_height = rc.bottom - rc.top;

  const ThermalSource &thermal = item.thermal;

  RasterPoint pt = { PixelScalar(rc.left + line_height / 2),
                     PixelScalar(rc.top + line_height / 2) };

  look.thermal_source_icon.Draw(canvas, pt);

  const Font &name_font = *dialog_look.list.font;
  const Font &small_font = *dialog_look.small_font;
  canvas.SetTextColor(COLOR_BLACK);

  PixelScalar left = rc.left + line_height + Layout::FastScale(2);

  canvas.Select(name_font);
  canvas.text_clipped(left, rc.top + Layout::FastScale(2), rc, _("Thermal"));

  StaticString<256> buffer;
  TCHAR lift_buffer[32], time_buffer[32], timespan_buffer[32];
  FormatUserVerticalSpeed(thermal.lift_rate, lift_buffer, 32);
  FormatSignedTimeHHMM(time_buffer, TimeLocal((int)thermal.time));

  int timespan = BrokenDateTime::NowUTC().GetSecondOfDay() - (int)thermal.time;
  if (timespan < 0)
    timespan += 24 * 60 * 60;

  FormatTimespanSmart(timespan_buffer, timespan);

  buffer.Format(_T("%s: %s"), _("Avg. lift"), lift_buffer);
  buffer.append(_T(" - "));
  buffer.AppendFormat(_("left %s ago"), timespan_buffer);
  buffer.AppendFormat(_T(" (%s)"), time_buffer);
  canvas.Select(small_font);
  canvas.text_clipped(left,
                      rc.top + name_font.GetHeight() + Layout::FastScale(4),
                      rc, buffer);
}
示例#10
0
void
FlarmTrafficControl::PaintClimbRate(Canvas &canvas, PixelRect rc,
                                    fixed climb_rate) const
{
  // Paint label
  canvas.Select(look.info_labels_font);
  const unsigned label_width = canvas.CalcTextSize(_("Vario")).cx;
  canvas.DrawText(rc.right - label_width, rc.top, _("Vario"));

  // Format climb rate
  TCHAR buffer[20];
  Unit unit = Units::GetUserVerticalSpeedUnit();
  FormatUserVerticalSpeed(climb_rate, buffer, false);

  // Calculate unit size
  canvas.Select(look.info_units_font);
  const unsigned unit_width = UnitSymbolRenderer::GetSize(canvas, unit).cx;
  const unsigned unit_height =
      UnitSymbolRenderer::GetAscentHeight(look.info_units_font, unit);

  UPixelScalar space_width = unit_width / 3;

  // Calculate value size
  canvas.Select(look.info_values_font);
  const unsigned value_height = look.info_values_font.GetAscentHeight();
  const unsigned value_width = canvas.CalcTextSize(buffer).cx;

  // Calculate positions
  const int max_height = std::max(unit_height, value_height);
  const int y = rc.top + look.info_units_font.GetHeight() + max_height;

  // Paint value
  canvas.DrawText(rc.right - unit_width - space_width - value_width,
                  y - value_height,
                  buffer);

  // Paint unit
  canvas.Select(look.info_units_font);
  UnitSymbolRenderer::Draw(canvas,
                           RasterPoint(rc.right - unit_width, y - unit_height),
                           unit, look.unit_fraction_pen);
}
示例#11
0
// MacCready
// Adjusts MacCready settings
// up, down, auto on, auto off, auto toggle, auto show
void
InputEvents::eventMacCready(const TCHAR *misc)
{
  if (protected_task_manager == NULL)
    return;

  const GlidePolar &polar =
    CommonInterface::GetComputerSettings().polar.glide_polar_task;
  fixed mc = polar.GetMC();

  TaskBehaviour &task_behaviour = CommonInterface::SetComputerSettings().task;

  if (StringIsEqual(misc, _T("up"))) {
    mc = std::min(mc + Units::ToSysVSpeed(GetUserVerticalSpeedStep()),
                  fixed(5));
    ActionInterface::SetManualMacCready(mc);
  } else if (StringIsEqual(misc, _T("down"))) {
    mc = std::max(mc - Units::ToSysVSpeed(GetUserVerticalSpeedStep()),
                  fixed_zero);
    ActionInterface::SetManualMacCready(mc);
  } else if (StringIsEqual(misc, _T("auto toggle"))) {
    task_behaviour.auto_mc = !task_behaviour.auto_mc;
    Profile::Set(szProfileAutoMc, task_behaviour.auto_mc);
  } else if (StringIsEqual(misc, _T("auto on"))) {
    task_behaviour.auto_mc = true;
    Profile::Set(szProfileAutoMc, true);
  } else if (StringIsEqual(misc, _T("auto off"))) {
    task_behaviour.auto_mc = false;
    Profile::Set(szProfileAutoMc, false);
  } else if (StringIsEqual(misc, _T("auto show"))) {
    if (task_behaviour.auto_mc) {
      Message::AddMessage(_("Auto. MacCready on"));
    } else {
      Message::AddMessage(_("Auto. MacCready off"));
    }
  } else if (StringIsEqual(misc, _T("show"))) {
    TCHAR Temp[100];
    FormatUserVerticalSpeed(mc, Temp, false);
    Message::AddMessage(_("MacCready "), Temp);
  }
}
示例#12
0
static void
Draw(Canvas &canvas, PixelRect rc,
     const TrafficMapItem &item,
     const TwoTextRowsRenderer &row_renderer,
     const TrafficLook &traffic_look,
     const TrafficList *traffic_list)
{
  const unsigned line_height = rc.GetHeight();
  const unsigned text_padding = Layout::GetTextPadding();

  const FlarmTraffic *traffic = traffic_list == nullptr
    ? nullptr
    : traffic_list->FindTraffic(item.id);

  const PixelPoint pt(rc.left + line_height / 2, rc.top + line_height / 2);

  // Render the representation of the traffic icon
  if (traffic != nullptr)
    TrafficRenderer::Draw(canvas, traffic_look, *traffic, traffic->track,
                          item.color, pt);

  rc.left += line_height + text_padding;

  // Now render the text information
  const FlarmNetRecord *record = FlarmDetails::LookupRecord(item.id);

  StaticString<256> title_string;
  if (record && !StringIsEmpty(record->pilot))
    title_string = record->pilot.c_str();
  else
    title_string = _("FLARM Traffic");

  // Append name to the title, if it exists
  const TCHAR *callsign = FlarmDetails::LookupCallsign(item.id);
  if (callsign != nullptr && !StringIsEmpty(callsign)) {
    title_string.append(_T(", "));
    title_string.append(callsign);
  }

  row_renderer.DrawFirstRow(canvas, rc, title_string);

  StaticString<256> info_string;
  if (record && !StringIsEmpty(record->plane_type))
    info_string = record->plane_type;
  else if (traffic != nullptr)
    info_string = FlarmTraffic::GetTypeString(traffic->type);
  else
    info_string = _("Unknown");

  // Generate the line of info about the target, if it's available
  if (traffic != nullptr) {
    if (traffic->altitude_available)
      info_string.AppendFormat(_T(", %s: %s"), _("Altitude"),
                               FormatUserAltitude(traffic->altitude).c_str());

    if (traffic->climb_rate_avg30s_available) {
      TCHAR tmp[15];
      FormatUserVerticalSpeed(traffic->climb_rate_avg30s, tmp, 15);
      info_string.AppendFormat(_T(", %s: %s"), _("Vario"), tmp);
    }
  }

  row_renderer.DrawSecondRow(canvas, rc, info_string);
}
示例#13
0
/**
 * Paints the traffic symbols on the given canvas
 * @param canvas The canvas to paint on
 */
void
FlarmTrafficWindow::PaintRadarTarget(Canvas &canvas,
                                     const FlarmTraffic &traffic,
                                     unsigned i)
{
  // Save relative East/North
  fixed x = traffic.relative_east;
  fixed y = -traffic.relative_north;

  // Calculate the distance in pixels
  fixed scale = RangeScale(traffic.distance);

  // Don't display distracting, far away targets in WarningMode
  if (WarningMode() && !traffic.HasAlarm() && scale == fixed(radius))
    return;

  // x and y are not between 0 and 1 (distance will be handled via scale)
  if (!traffic.distance.IsZero()) {
    x /= traffic.distance;
    y /= traffic.distance;
  } else {
    x = fixed(0);
    y = fixed(0);
  }

  if (!enable_north_up) {
    // Rotate x and y to have a track up display
    FastRotation::Pair p = fr.Rotate(x, y);
    x = fixed(p.first);
    y = fixed(p.second);
  }

  // Calculate screen coordinates
  sc[i].x = radar_mid.x + iround(x * scale);
  sc[i].y = radar_mid.y + iround(y * scale);

  const Color *text_color;
  const Pen *target_pen, *circle_pen;
  const Brush *target_brush, *arrow_brush;
  bool hollow_brush = false;
  unsigned circles = 0;

  // Set the arrow color depending on alarm level
  switch (traffic.alarm_level) {
  case FlarmTraffic::AlarmType::LOW:
  case FlarmTraffic::AlarmType::INFO_ALERT:
    text_color = &look.default_color;
    target_pen = circle_pen = &look.warning_pen;
    target_brush = &look.warning_brush;
    arrow_brush = &look.default_brush;
    circles = 1;
    break;
  case FlarmTraffic::AlarmType::IMPORTANT:
  case FlarmTraffic::AlarmType::URGENT:
    text_color = &look.default_color;
    target_pen = circle_pen = &look.alarm_pen;
    target_brush = &look.alarm_brush;
    arrow_brush = &look.default_brush;
    circles = 2;
    break;
  case FlarmTraffic::AlarmType::NONE:
    if (WarningMode()) {
      text_color = &look.passive_color;
      target_pen = &look.passive_pen;
      arrow_brush = &look.passive_brush;
      hollow_brush = true;
    } else {
      // Search for team color
      const FlarmColor team_color = FlarmFriends::GetFriendColor(traffic.id);

      // If team color found -> draw a colored circle around the target
      if (team_color != FlarmColor::NONE) {
        switch (team_color) {
        case FlarmColor::GREEN:
          circle_pen = &look.team_pen_green;
          break;
        case FlarmColor::BLUE:
          circle_pen = &look.team_pen_blue;
          break;
        case FlarmColor::YELLOW:
          circle_pen = &look.team_pen_yellow;
          break;
        case FlarmColor::MAGENTA:
          circle_pen = &look.team_pen_magenta;
          break;
        default:
          break;
        }

        circles = 1;
      }

      if (!small && static_cast<unsigned> (selection) == i) {
        text_color = &look.selection_color;
        target_brush = arrow_brush = &look.selection_brush;
        target_pen = &look.selection_pen;
      } else {
        hollow_brush = true;
        if (traffic.IsPassive()) {
          text_color = &look.passive_color;
          target_pen = &look.passive_pen;
          arrow_brush = &look.passive_brush;
        } else {
          text_color = &look.default_color;
          target_pen = &look.default_pen;
          arrow_brush = &look.default_brush;
        }
      }
    }
    break;
  }

  if (circles > 0) {
    canvas.SelectHollowBrush();
    canvas.Select(*circle_pen);
    canvas.DrawCircle(sc[i].x, sc[i].y, Layout::FastScale(small ? 8 : 16));
    if (circles == 2)
      canvas.DrawCircle(sc[i].x, sc[i].y, Layout::FastScale(small ? 10 : 19));
  }

  // Create an arrow polygon
  RasterPoint Arrow[5];
  if (small) {
    Arrow[0].x = -3;
    Arrow[0].y = 4;
    Arrow[1].x = 0;
    Arrow[1].y = -5;
    Arrow[2].x = 3;
    Arrow[2].y = 4;
    Arrow[3].x = 0;
    Arrow[3].y = 2;
    Arrow[4].x = -3;
    Arrow[4].y = 4;
  } else {
    Arrow[0].x = -6;
    Arrow[0].y = 8;
    Arrow[1].x = 0;
    Arrow[1].y = -10;
    Arrow[2].x = 6;
    Arrow[2].y = 8;
    Arrow[3].x = 0;
    Arrow[3].y = 5;
    Arrow[4].x = -6;
    Arrow[4].y = 8;
  }

  // Rotate and shift the arrow
  PolygonRotateShift(Arrow, 5, sc[i].x, sc[i].y,
                     traffic.track - (enable_north_up ?
                                             Angle::Zero() : heading));

  // Select pen and brush
  canvas.Select(*target_pen);
  if (hollow_brush)
    canvas.SelectHollowBrush();
  else
    canvas.Select(*target_brush);

  // Draw the polygon
  canvas.DrawPolygon(Arrow, 5);

  if (small) {
    if (!WarningMode() || traffic.HasAlarm())
      PaintTargetInfoSmall(canvas, traffic, i, *text_color, *arrow_brush);

    return;
  }

  // if warning exists -> don't draw vertical speeds
  if (WarningMode())
    return;

  // if vertical speed to small or negative -> skip this one
  if (side_display_type == SIDE_INFO_VARIO &&
      (!traffic.climb_rate_avg30s_available ||
       traffic.climb_rate_avg30s < fixed(0.5) ||
       traffic.IsPowered()))
      return;

  // Select font
  canvas.SetBackgroundTransparent();
  canvas.Select(look.side_info_font);

  // Format string
  TCHAR tmp[10];

  if (side_display_type == SIDE_INFO_VARIO)
    FormatUserVerticalSpeed(traffic.climb_rate_avg30s, tmp, false);
  else
    FormatRelativeUserAltitude(traffic.relative_altitude, tmp, true);

  PixelSize sz = canvas.CalcTextSize(tmp);

  // Draw vertical speed shadow
  canvas.SetTextColor(COLOR_WHITE);
  canvas.DrawText(sc[i].x + Layout::FastScale(11) + 1,
                  sc[i].y - sz.cy / 2 + 1, tmp);
  canvas.DrawText(sc[i].x + Layout::FastScale(11) - 1,
                  sc[i].y - sz.cy / 2 - 1, tmp);

  // Select color
  canvas.SetTextColor(*text_color);

  // Draw vertical speed
  canvas.DrawText(sc[i].x + Layout::FastScale(11), sc[i].y - sz.cy / 2, tmp);
}
示例#14
0
/**
 * Paints the basic info for the selected target on the given canvas
 * @param canvas The canvas to paint on
 */
void
FlarmTrafficControl2::PaintTrafficInfo(Canvas &canvas) const
{
  // Don't paint numbers if no plane selected
  if (selection == -1 && !WarningMode())
    return;

  // Shortcut to the selected traffic
  FlarmTraffic traffic = data.list[WarningMode() ? warning : selection];
  assert(traffic.IsDefined());

  // Temporary string
  TCHAR tmp[20];
  // Temporary string size
  PixelSize sz;

  PixelRect rc;
  rc.left = padding;
  rc.top = padding;
  rc.right = canvas.get_width() - padding;
  rc.bottom = canvas.get_height() - padding;

  // Set the text color and background
  switch (traffic.alarm_level) {
  case FlarmTraffic::AlarmType::LOW:
  case FlarmTraffic::AlarmType::INFO_ALERT:
    canvas.SetTextColor(look.warning_color);
    break;
  case FlarmTraffic::AlarmType::IMPORTANT:
  case FlarmTraffic::AlarmType::URGENT:
    canvas.SetTextColor(look.alarm_color);
    break;
  case FlarmTraffic::AlarmType::NONE:
    canvas.SetTextColor(look.default_color);
    break;
  }

  canvas.SetBackgroundTransparent();

  // Climb Rate
  if (!WarningMode() && traffic.climb_rate_avg30s_available) {
    FormatUserVerticalSpeed(traffic.climb_rate_avg30s, tmp, 20);
    canvas.Select(look.info_values_font);
    sz = canvas.CalcTextSize(tmp);
    canvas.text(rc.right - sz.cx, rc.top + look.info_labels_font.GetHeight(), tmp);

    canvas.Select(look.info_labels_font);
    sz = canvas.CalcTextSize(_("Vario"));
    canvas.text(rc.right - sz.cx, rc.top, _("Vario"));
  }

  // Distance
  FormatUserDistanceSmart(traffic.distance, tmp, 20, fixed(1000));
  canvas.Select(look.info_values_font);
  sz = canvas.CalcTextSize(tmp);
  canvas.text(rc.left, rc.bottom - sz.cy, tmp);

  canvas.Select(look.info_labels_font);
  canvas.text(rc.left,
              rc.bottom - look.info_values_font.GetHeight() - look.info_labels_font.GetHeight(),
              _("Distance"));

  // Relative Height
  FormatRelativeUserAltitude(traffic.relative_altitude, tmp, 20);
  canvas.Select(look.info_values_font);
  sz = canvas.CalcTextSize(tmp);
  canvas.text(rc.right - sz.cx, rc.bottom - sz.cy, tmp);

  canvas.Select(look.info_labels_font);
  sz = canvas.CalcTextSize(_("Rel. Alt."));
  canvas.text(rc.right - sz.cx,
              rc.bottom - look.info_values_font.GetHeight() - look.info_labels_font.GetHeight(),
              _("Rel. Alt."));

  // ID / Name
  unsigned font_size;
  if (traffic.HasName()) {
    canvas.Select(look.call_sign_font);
    font_size = look.call_sign_font.GetHeight();

    if (!traffic.HasAlarm())
      canvas.SetTextColor(look.selection_color);

    _tcscpy(tmp, traffic.name);
  } else {
    font_size = look.info_labels_font.GetHeight();
    traffic.id.Format(tmp);
  }

  if (!WarningMode()) {
    // Team color dot
    FlarmFriends::Color team_color = FlarmFriends::GetFriendColor(traffic.id);

    // If no color found but target is teammate
    if (team_color == FlarmFriends::Color::NONE &&
        settings.team_flarm_tracking &&
        traffic.id == settings.team_flarm_id)
      // .. use yellow color
      team_color = FlarmFriends::Color::GREEN;

    // If team color found -> draw a colored circle around the target
    if (team_color != FlarmFriends::Color::NONE) {
      switch (team_color) {
      case FlarmFriends::Color::GREEN:
        canvas.Select(look.team_brush_green);
        break;
      case FlarmFriends::Color::BLUE:
        canvas.Select(look.team_brush_blue);
        break;
      case FlarmFriends::Color::YELLOW:
        canvas.Select(look.team_brush_green);
        break;
      case FlarmFriends::Color::MAGENTA:
        canvas.Select(look.team_brush_magenta);
        break;
      default:
        break;
      }

      canvas.SelectNullPen();
      canvas.DrawCircle(rc.left + Layout::FastScale(7), rc.top + (font_size / 2),
                    Layout::FastScale(7));

      rc.left += Layout::FastScale(16);
    }
  }

  canvas.text(rc.left, rc.top, tmp);
}
示例#15
0
void
InfoBoxData::SetCommentFromVerticalSpeed(fixed new_value, bool include_sign)
{
  FormatUserVerticalSpeed(new_value, comment.buffer(), true, include_sign);
}
示例#16
0
/**
 * Draws the FLARM traffic icons onto the given canvas
 * @param canvas Canvas for drawing
 */
void
MapWindow::DrawFLARMTraffic(Canvas &canvas,
                            const PixelPoint aircraft_pos) const
{
  // Return if FLARM icons on moving map are disabled
  if (!GetMapSettings().show_flarm_on_map)
    return;

  // Return if FLARM data is not available
  const TrafficList &flarm = Basic().flarm.traffic;
  if (flarm.IsEmpty())
    return;

  const WindowProjection &projection = render_projection;

  // if zoomed in too far out, dont draw traffic since it will be too close to
  // the glider and so will be meaningless (serves only to clutter, cant help
  // the pilot)
  if (projection.GetMapScale() > 7300)
    return;

  canvas.Select(*traffic_look.font);

  // Circle through the FLARM targets
  for (auto it = flarm.list.begin(), end = flarm.list.end();
      it != end; ++it) {
    const FlarmTraffic &traffic = *it;

    if (!traffic.location_available)
      continue;

    // Save the location of the FLARM target
    GeoPoint target_loc = traffic.location;

    // Points for the screen coordinates for the icon, name and average climb
    PixelPoint sc, sc_name, sc_av;

    // If FLARM target not on the screen, move to the next one
    if (!projection.GeoToScreenIfVisible(target_loc, sc))
      continue;

    // Draw the name 16 points below the icon
    sc_name = sc;
    sc_name.y -= Layout::Scale(20);

    // Draw the average climb value above the icon
    sc_av = sc;
    sc_av.y += Layout::Scale(5);

    TextInBoxMode mode;
    mode.shape = LabelShape::OUTLINED;

    // JMW TODO enhancement: decluttering of FLARM altitudes (sort by max lift)

    int dx = sc_av.x - aircraft_pos.x;
    int dy = sc_av.y - aircraft_pos.y;

    // only draw labels if not close to aircraft
    if (dx * dx + dy * dy > Layout::Scale(30 * 30)) {
      // If FLARM callsign/name available draw it to the canvas
      if (traffic.HasName() && !StringIsEmpty(traffic.name))
        TextInBox(canvas, traffic.name, sc_name.x, sc_name.y,
                  mode, GetClientRect());

      if (traffic.climb_rate_avg30s >= 0.1) {
        // If average climb data available draw it to the canvas
        TCHAR label_avg[100];
        FormatUserVerticalSpeed(traffic.climb_rate_avg30s,
                                       label_avg, false);
        TextInBox(canvas, label_avg, sc_av.x, sc_av.y, mode, GetClientRect());
      }
    }

    auto color = FlarmFriends::GetFriendColor(traffic.id);
    TrafficRenderer::Draw(canvas, traffic_look, traffic,
                          traffic.track - projection.GetScreenAngle(),
                          color, sc);
  }
}
示例#17
0
/**
 * Draws the GliderLink traffic icons onto the given canvas
 * @param canvas Canvas for drawing
 */
void
MapWindow::DrawGLinkTraffic(Canvas &canvas,
                            const PixelPoint aircraft_pos) const
{
#ifdef ANDROID

  // Return if FLARM icons on moving map are disabled
  if (!GetMapSettings().show_flarm_on_map)
    return;

  const GliderLinkTrafficList &traffic = Basic().glink_data.traffic;
  if (traffic.IsEmpty())
    return;

  const MoreData &basic = Basic();

  const WindowProjection &projection = render_projection;

  canvas.Select(*traffic_look.font);

  // Circle through the GliderLink targets
  for (const auto &traf : traffic.list) {

    // Save the location of the target
    GeoPoint target_loc = traf.location;

    // Points for the screen coordinates for the icon, name and average climb
    PixelPoint sc, sc_name, sc_av, sc_alt;

    // If FLARM target not on the screen, move to the next one
    if (!projection.GeoToScreenIfVisible(target_loc, sc))
      continue;

    // Draw the callsign above the icon
    sc_name = sc;
    sc_name.x -= Layout::Scale(10);
    sc_name.y -= Layout::Scale(15);

    // Draw the average climb to the right of the icon
    sc_av = sc;
    sc_av.x += Layout::Scale(10);
    sc_av.y -= Layout::Scale(8);

    // Location of altitude label
    sc_alt = sc;
    sc_alt.x -= Layout::Scale(10);
    sc_alt.y -= Layout::Scale(0);

    TextInBoxMode mode;
    mode.shape = LabelShape::OUTLINED;
    mode.align = TextInBoxMode::Alignment::RIGHT;

    // If callsign/name available draw it to the canvas
    if (traf.HasName() && !StringIsEmpty(traf.name))
      TextInBox(canvas, traf.name, sc_name.x, sc_name.y,
                mode, GetClientRect());

    if (traf.climb_rate_received) {

      // If average climb data available draw it to the canvas
      TCHAR label_avg[100];
      FormatUserVerticalSpeed(traf.climb_rate,
                                     label_avg, false);
      mode.align = TextInBoxMode::Alignment::LEFT;
      TextInBox(canvas, label_avg, sc_av.x, sc_av.y, mode, GetClientRect());
    }

    // use GPS altitude to be consistent with GliderLink
    if(basic.gps_altitude_available && traf.altitude_received
        && fabs(double(traf.altitude) - basic.gps_altitude) >= 100.0) {
      // If average climb data available draw it to the canvas
      TCHAR label_alt[100];
      double alt = (double(traf.altitude) - basic.gps_altitude) / 100.0;
      FormatRelativeUserAltitude(alt, label_alt, false);

      mode.align = TextInBoxMode::Alignment::RIGHT;
      TextInBox(canvas, label_alt, sc_alt.x, sc_alt.y, mode, GetClientRect());
    }

    TrafficRenderer::Draw(canvas, traffic_look, traf,
                          traf.track - projection.GetScreenAngle(), sc);
  }
#endif
}
示例#18
0
void
VarioBarRenderer::Draw(Canvas &canvas, const PixelRect &rc,
                            const MoreData &basic,
                            const DerivedInfo &calculated,
                            const GlidePolar &glide_polar,
                            const bool vario_bar_avg_enabled) const
{
#ifdef ENABLE_OPENGL
  const ScopeAlphaBlend alpha_blend;
#endif

  RasterPoint VarioBar[6] = {
      { 0, 0 }, { 9, -9 }, { 18, 0 }, { 18, 0 }, { 9, 0 }, { 0, 0 }
  };
  RasterPoint VarioBarAvg[4] = {
      { 0, 0 }, { 9, -9 }, { 9, 0 }, { 0, 0 }
  };
  RasterPoint clipping_arrow[6] = {
      { 0, 0 }, { 9, 9 }, { 18, 0 }, { 18, 6 }, { 9, 15 }, { 0, 6 }
  };
  RasterPoint clipping_arrow_av[4] = {
      { 0, 0 }, { 9, 9 }, { 9, 15 }, { 0, 6 }
  };
  RasterPoint mc_arrow[6] = {
      { 0, 0 }, { 9, -9 }, { 18, 0 }, { 18, -2 }, { 9, -11 }, { 0, -2 }
  };

  TCHAR Value[10];

  const int y0 = (rc.bottom + rc.top) / 2;

  /* NOTE: size_divisor replaces the fixed value 9 that was used throughout
   * the code sink which caused fixed size rendering regardless of
   * map size (except the effects of Layout::Scale()). This method
   * is not usable with variable map sizes (e.g. because of the cross-section
   * area). size_divisor is used to introduce a screen size dependent scaling.
   * That workaround is an ugly hack and needs a rework. */
  const auto size_divisor =
    std::max((fixed) Layout::Scale(70 / (rc.bottom - rc.top)), fixed(0.15));

  PixelScalar dy_variobar = 0;
  PixelScalar dy_variobar_av = 0;
  PixelScalar dy_variobar_mc = 0;

  PixelScalar clipping_arrow_offset = Layout::Scale(4);
  PixelScalar clipping_arrow_av_offset = Layout::Scale(4);
//  PixelScalar clipping_arrow_mc_offset = Layout::Scale(4);

  auto vario_gross = basic.brutto_vario;

  FormatUserVerticalSpeed(vario_gross, Value, false, true);
  canvas.Select(*look.font);
  const PixelSize text_size = canvas.CalcTextSize(Value);

  auto vario_avg = calculated.average;

  // cut vario_gross at +- 5 meters/s
  if (vario_gross > fixed(5))
    vario_gross = fixed(5);
  if (vario_gross < fixed(-5))
    vario_gross = fixed(-5);

  int Offset = (int)(vario_gross / size_divisor);

  Offset = Layout::Scale(Offset);
  if (!positive(vario_gross)) {
    VarioBar[1].y = Layout::Scale(9);
    dy_variobar = text_size.cy + 2;
  } else {
    VarioBar[1].y = -Layout::Scale(9);
    clipping_arrow[1].y = -clipping_arrow[1].y;
    clipping_arrow[3].y = -clipping_arrow[3].y;
    clipping_arrow[4].y = -clipping_arrow[4].y;
    clipping_arrow[5].y = -clipping_arrow[5].y;
    clipping_arrow_offset = -clipping_arrow_offset;
    dy_variobar = -1;
  }

  // cut vario_avg at +- 5 meters/s
  if (vario_avg > fixed(5))
    vario_avg = fixed(5);
  if (vario_avg < fixed(-5))
    vario_avg = fixed(-5);

  int OffsetAvg = int(vario_avg / size_divisor);

  OffsetAvg = Layout::Scale(OffsetAvg);
  if (!positive(vario_avg)) {
    VarioBarAvg[1].y = Layout::Scale(9);
    dy_variobar_av = text_size.cy + 2;
  } else {
    VarioBarAvg[1].y = -Layout::Scale(9);
    clipping_arrow_av[1].y = -clipping_arrow_av[1].y;
    clipping_arrow_av[2].y = -clipping_arrow_av[2].y;
    clipping_arrow_av[3].y = -clipping_arrow_av[3].y;
    clipping_arrow_av_offset = -clipping_arrow_av_offset;
    dy_variobar_av = -1;
  }


  //clip MC Value
  auto mc_val = glide_polar.GetMC();
  if (mc_val > fixed(5))
    mc_val = fixed(5);
  if (!positive(mc_val)) {
    dy_variobar_mc = text_size.cy + 2;
  }else{
    dy_variobar_mc = -1;
  }

  int OffsetMC = int(mc_val / size_divisor);
  OffsetMC = Layout::Scale(OffsetMC);

  for (unsigned i = 0; i < 6; i++) {
    VarioBar[i].y += y0 + dy_variobar;
    VarioBar[i].x = rc.right - Layout::Scale(VarioBar[i].x);
  }

  VarioBar[0].y -= Offset;
  VarioBar[1].y -= Offset;
  VarioBar[2].y -= Offset;

  for (unsigned i = 0; i < 4; i++) {
    VarioBarAvg[i].y += y0 + dy_variobar_av;
    VarioBarAvg[i].x = rc.right-Layout::Scale(VarioBarAvg[i].x);
  }

  VarioBarAvg[0].y -= OffsetAvg;
  VarioBarAvg[1].y -= OffsetAvg;

  // prepare mc arrow
  for (unsigned i = 0; i < 6; i++) {
    mc_arrow[i].y = Layout::Scale(mc_arrow[i].y) + y0
      - OffsetMC + dy_variobar_mc;
    mc_arrow[i].x = rc.right - Layout::Scale(mc_arrow[i].x);
  }

  // prepare clipping arrow
  for (unsigned i = 0; i < 6; i++) {
    clipping_arrow[i].y = Layout::Scale(clipping_arrow[i].y) + y0 - Offset
      + clipping_arrow_offset + dy_variobar;
    clipping_arrow[i].x = rc.right - Layout::Scale(clipping_arrow[i].x);
  }

  // prepare clipping avg
  for (unsigned i = 0; i < 4; i++) {
    clipping_arrow_av[i].y = Layout::Scale(clipping_arrow_av[i].y) + y0 - OffsetAvg
      + clipping_arrow_av_offset + dy_variobar_av;
    clipping_arrow_av[i].x = rc.right-Layout::Scale(clipping_arrow_av[i].x);
  }

  // draw actual vario bar
  if (!positive(vario_gross)) {
    canvas.Select(look.pen_sink);
    canvas.Select(look.brush_sink);
  } else {
    canvas.Select(look.pen_climb);
    canvas.Select(look.brush_climb);
  }

  canvas.DrawPolygon(VarioBar, 6);

  // draw clipping arrow
  if (vario_gross <= fixed(-5) || vario_gross >= fixed(5))
    canvas.DrawPolygon(clipping_arrow, 6);

  // draw avg vario bar
  if (!positive(vario_avg) && vario_bar_avg_enabled) {
      canvas.Select(look.pen_sink);
      canvas.Select(look.brush_sink_avg);
  } else {
    canvas.Select(look.pen_climb);
    canvas.Select(look.brush_climb_avg);
  }

  canvas.DrawPolygon(VarioBarAvg, 4);

  if (vario_avg <= fixed(-5.0) || vario_avg >= fixed(5.0))
      canvas.DrawPolygon(clipping_arrow_av, 4);

  //draw MC arrow
  canvas.Select(look.pen_mc);
  canvas.Select(look.brush_mc);

  canvas.DrawPolygon(mc_arrow, 6);

  //draw text
  canvas.SetTextColor(COLOR_BLACK);
  canvas.SetBackgroundColor(COLOR_WHITE);

  TextInBoxMode style;
  style.shape = LabelShape::ROUNDED_BLACK;
  style.move_in_view = true;

  if (text_size.cx < Layout::Scale(18)) {
    style.align = TextInBoxMode::Alignment::RIGHT;
    TextInBox(canvas, Value, rc.right, y0, style, rc);
  } else
    TextInBox(canvas, Value, rc.right-Layout::Scale(18), y0, style, rc);
}
示例#19
0
/**
 * Paints the traffic symbols on the given canvas
 * @param canvas The canvas to paint on
 */
void
FlarmTrafficWindow::PaintRadarTarget(Canvas &canvas,
                                     const FlarmTraffic &traffic,
                                     unsigned i)
{
  // Save relative East/North
  fixed x = traffic.relative_east;
  fixed y = -traffic.relative_north;

  // Calculate the distance in pixels
  fixed scale = RangeScale(traffic.distance);

  // Don't display distracting, far away targets in WarningMode
  if (WarningMode() && !traffic.HasAlarm() && scale == fixed(radius))
    return;

  // x and y are not between 0 and 1 (distance will be handled via scale)
  if (!traffic.distance.IsZero()) {
    x /= traffic.distance;
    y /= traffic.distance;
  } else {
    x = fixed_zero;
    y = fixed_zero;
  }

  if (!enable_north_up) {
    // Rotate x and y to have a track up display
    FastRotation::Pair p = fr.Rotate(x, y);
    x = fixed(p.first);
    y = fixed(p.second);
  }

  // Calculate screen coordinates
  sc[i].x = radar_mid.x + iround(x * scale);
  sc[i].y = radar_mid.y + iround(y * scale);

  const Color *text_color;
  const Pen *target_pen, *circle_pen;
  const Brush *target_brush, *arrow_brush;
  bool hollow_brush = false;
  unsigned circles = 0;

  // Set the arrow color depending on alarm level
  switch (traffic.alarm_level) {
  case FlarmTraffic::AlarmType::LOW:
    text_color = &look.default_color;
    target_pen = circle_pen = &look.warning_pen;
    target_brush = &look.warning_brush;
    arrow_brush = &look.default_brush;
    circles = 1;
    break;
  case FlarmTraffic::AlarmType::IMPORTANT:
  case FlarmTraffic::AlarmType::URGENT:
    text_color = &look.default_color;
    target_pen = circle_pen = &look.alarm_pen;
    target_brush = &look.alarm_brush;
    arrow_brush = &look.default_brush;
    circles = 2;
    break;
  case FlarmTraffic::AlarmType::NONE:
    if (WarningMode()) {
      text_color = &look.passive_color;
      target_pen = &look.passive_pen;
      arrow_brush = &look.passive_brush;
      hollow_brush = true;
    } else {
      // Search for team color
      FlarmFriends::Color team_color = FlarmFriends::GetFriendColor(traffic.id);

      // If no color found but target is teammate
      if (team_color == FlarmFriends::NONE &&
          settings.team_flarm_tracking &&
          traffic.id == settings.team_flarm_id)
        // .. use yellow color
        team_color = FlarmFriends::GREEN;

      // If team color found -> draw a colored circle around the target
      if (team_color != FlarmFriends::NONE) {
        switch (team_color) {
        case FlarmFriends::GREEN:
          circle_pen = &look.team_pen_green;
          break;
        case FlarmFriends::BLUE:
          circle_pen = &look.team_pen_blue;
          break;
        case FlarmFriends::YELLOW:
          circle_pen = &look.team_pen_yellow;
          break;
        case FlarmFriends::MAGENTA:
          circle_pen = &look.team_pen_magenta;
          break;
        default:
          break;
        }

        circles = 1;
      }

      if (!small && static_cast<unsigned> (selection) == i) {
        text_color = &look.selection_color;
        target_brush = arrow_brush = &look.selection_brush;
        target_pen = &look.selection_pen;
      } else {
        hollow_brush = true;
        if (traffic.IsPassive()) {
          text_color = &look.passive_color;
          target_pen = &look.passive_pen;
          arrow_brush = &look.passive_brush;
        } else {
          text_color = &look.default_color;
          target_pen = &look.default_pen;
          arrow_brush = &look.default_brush;
        }
      }
    }
    break;
  }

  if (circles > 0) {
    canvas.SelectHollowBrush();
    canvas.Select(*circle_pen);
    canvas.circle(sc[i].x, sc[i].y, Layout::FastScale(small ? 8 : 16));
    if (circles == 2)
      canvas.circle(sc[i].x, sc[i].y, Layout::FastScale(small ? 10 : 19));
  }

  // Create an arrow polygon
  RasterPoint Arrow[5];
  if (small) {
    Arrow[0].x = -3;
    Arrow[0].y = 4;
    Arrow[1].x = 0;
    Arrow[1].y = -5;
    Arrow[2].x = 3;
    Arrow[2].y = 4;
    Arrow[3].x = 0;
    Arrow[3].y = 2;
    Arrow[4].x = -3;
    Arrow[4].y = 4;
  } else {
    Arrow[0].x = -6;
    Arrow[0].y = 8;
    Arrow[1].x = 0;
    Arrow[1].y = -10;
    Arrow[2].x = 6;
    Arrow[2].y = 8;
    Arrow[3].x = 0;
    Arrow[3].y = 5;
    Arrow[4].x = -6;
    Arrow[4].y = 8;
  }

  // Rotate and shift the arrow
  PolygonRotateShift(Arrow, 5, sc[i].x, sc[i].y,
                     traffic.track - (enable_north_up ?
                                             Angle::Zero() : heading));

  // Select pen and brush
  canvas.Select(*target_pen);
  if (hollow_brush)
    canvas.SelectHollowBrush();
  else
    canvas.Select(*target_brush);

  // Draw the polygon
  canvas.polygon(Arrow, 5);

  if (small) {
    if (WarningMode() && !traffic.HasAlarm())
      return;

    const short relalt =
        iround(Units::ToUserAltitude(traffic.relative_altitude) / 100);

    // if (relative altitude is other than zero)
    if (relalt == 0)
      return;

    // Write the relativ altitude devided by 100 to the Buffer
    StaticString<10> buffer;
    buffer.Format(_T("%d"), abs(relalt));

    // Select font
    canvas.SetBackgroundTransparent();
    canvas.Select(look.side_info_font);
    canvas.SetTextColor(*text_color);

    // Calculate size of the output string
    PixelSize tsize = canvas.CalcTextSize(buffer);

    UPixelScalar dist = Layout::FastScale(traffic.HasAlarm() ? 12 : 8);

    // Draw string
    canvas.text(sc[i].x + dist, sc[i].y - tsize.cy / 2, buffer);

    // Set target_brush for the up/down arrow
    canvas.Select(*arrow_brush);
    canvas.SelectNullPen();

    // Prepare the triangular polygon
    RasterPoint triangle[4];
    triangle[0].x = 0;
    triangle[0].y = -4;
    triangle[1].x = 3;
    triangle[1].y = 0;
    triangle[2].x = -3;
    triangle[2].y = 0;

    // Flip = -1 for arrow pointing downwards
    short flip = 1;
    if (relalt < 0)
      flip = -1;

    // Shift the arrow to the right position
    for (int j = 0; j < 3; j++) {
      triangle[j].x = Layout::FastScale(triangle[j].x);
      triangle[j].y = Layout::FastScale(triangle[j].y);

      triangle[j].x = sc[i].x + dist + triangle[j].x + tsize.cx / 2;
      triangle[j].y = sc[i].y + flip * (triangle[j].y  - tsize.cy / 2);
    }
    triangle[3].x = triangle[0].x;
    triangle[3].y = triangle[0].y;

    // Draw the arrow
    canvas.DrawTriangleFan(triangle, 4);

    return;
  }

  // if warning exists -> don't draw vertical speeds
  if (WarningMode())
    return;

  // if vertical speed to small or negative -> skip this one
  if (side_display_type == 1 &&
      (!traffic.climb_rate_avg30s_available ||
       traffic.climb_rate_avg30s < fixed(0.5) ||
       traffic.IsPowered()))
      return;

  // Select font
  canvas.SetBackgroundTransparent();
  canvas.Select(look.side_info_font);

  // Format string
  TCHAR tmp[10];

  if (side_display_type == 1)
    FormatUserVerticalSpeed(traffic.climb_rate_avg30s, tmp, false);
  else
    FormatRelativeUserAltitude(traffic.relative_altitude, tmp, true);

  PixelSize sz = canvas.CalcTextSize(tmp);

  // Draw vertical speed shadow
  canvas.SetTextColor(COLOR_WHITE);
  canvas.text(sc[i].x + Layout::FastScale(11) + 1,
              sc[i].y - sz.cy / 2 + 1, tmp);
  canvas.text(sc[i].x + Layout::FastScale(11) - 1,
              sc[i].y - sz.cy / 2 - 1, tmp);

  // Select color
  canvas.SetTextColor(*text_color);

  // Draw vertical speed
  canvas.text(sc[i].x + Layout::FastScale(11), sc[i].y - sz.cy / 2, tmp);
}