chromium/ash/hud_display/reference_lines.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/hud_display/reference_lines.h"

#include "ash/hud_display/hud_constants.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "cc/paint/paint_flags.h"
#include "third_party/skia/include/core/SkBlendMode.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/text_constants.h"

namespace ash {
namespace hud_display {

namespace {

constexpr SkColor kHUDGraphReferenceLineColor = SkColorSetRGB(162, 162, 220);

std::u16string GenerateLabelText(float value, const std::u16string& dimention) {
  if (value == (int)value) {
    return base::ASCIIToUTF16(base::StringPrintf("%d ", (int)value).c_str()) +
           dimention;
  } else {
    return base::ASCIIToUTF16(base::StringPrintf("%.2f ", value).c_str()) +
           dimention;
  }
}

}  // anonymous namespace

BEGIN_METADATA(ReferenceLines)
END_METADATA

ReferenceLines::ReferenceLines(float left,
                               float top,
                               float right,
                               float bottom,
                               const std::u16string& x_unit,
                               const std::u16string& y_unit,
                               int horizontal_points_number,
                               int horizontal_ticks_interval,
                               float vertical_ticks_interval)
    : color_(kHUDGraphReferenceLineColor),
      left_(left),
      top_(top),
      right_(right),
      bottom_(bottom),
      x_unit_(x_unit),
      y_unit_(y_unit),
      horizontal_points_number_(horizontal_points_number),
      horizontal_ticks_interval_(horizontal_ticks_interval),
      vertical_ticks_interval_(vertical_ticks_interval) {
  // Text is set later.
  right_top_label_ = AddChildView(std::make_unique<views::Label>(
      std::u16string(), views::style::CONTEXT_LABEL));
  right_middle_label_ = AddChildView(std::make_unique<views::Label>(
      std::u16string(), views::style::CONTEXT_LABEL));
  right_bottom_label_ = AddChildView(std::make_unique<views::Label>(
      std::u16string(), views::style::CONTEXT_LABEL));
  left_bottom_label_ = AddChildView(std::make_unique<views::Label>(
      std::u16string(), views::style::CONTEXT_LABEL));

  // Set label text.
  SetTopLabel(top_);
  SetBottomLabel(bottom_);
  SetLeftLabel(left_);

  right_top_label_->SetEnabledColor(color_);
  right_top_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);

  right_middle_label_->SetEnabledColor(color_);
  right_middle_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);

  right_bottom_label_->SetEnabledColor(color_);
  right_bottom_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);

  left_bottom_label_->SetEnabledColor(color_);
  left_bottom_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
}

ReferenceLines::~ReferenceLines() = default;

void ReferenceLines::Layout(PassKey) {
  // Align all the right labels on their left edge.
  gfx::Size right_top_label_size = right_top_label_->GetPreferredSize(
      views::SizeBounds(right_top_label_->width(), {}));
  gfx::Size right_middle_label_size = right_middle_label_->GetPreferredSize(
      views::SizeBounds(right_middle_label_->width(), {}));
  gfx::Size right_bottom_label_size = right_bottom_label_->GetPreferredSize(
      views::SizeBounds(right_bottom_label_->width(), {}));

  const int right_labels_width = std::max(
      right_top_label_size.width(), std::max(right_middle_label_size.width(),
                                             right_bottom_label_size.width()));
  right_top_label_size.set_width(right_labels_width);
  right_middle_label_size.set_width(right_labels_width);
  right_bottom_label_size.set_width(right_labels_width);

  right_top_label_->SetSize(right_top_label_size);
  right_middle_label_->SetSize(right_middle_label_size);
  right_bottom_label_->SetSize(right_bottom_label_size);

  left_bottom_label_->SetSize(left_bottom_label_->GetPreferredSize(
      views::SizeBounds(left_bottom_label_->width(), {})));

  constexpr int label_border = 3;  // Offset to labels from the reference lines.

  const gfx::Point right_top_label_position(
      bounds().width() - right_top_label_size.width() - label_border,
      label_border);
  const gfx::Point right_middle_label_position(
      bounds().width() - right_middle_label_size.width() - label_border,
      bounds().height() / 2 - right_middle_label_size.height() - label_border);
  const gfx::Point right_bottom_label_position(
      bounds().width() - right_bottom_label_size.width() - label_border,
      bounds().height() - right_bottom_label_size.height() - label_border);

  right_top_label_->SetPosition(right_top_label_position);
  right_middle_label_->SetPosition(right_middle_label_position);
  right_bottom_label_->SetPosition(right_bottom_label_position);

  left_bottom_label_->SetPosition(
      {label_border, bounds().height() -
                         left_bottom_label_
                             ->GetPreferredSize(views::SizeBounds(
                                 left_bottom_label_->width(), {}))
                             .height() -
                         label_border});

  LayoutSuperclass<views::View>(this);
}

void ReferenceLines::OnPaint(gfx::Canvas* canvas) {
  SkPath dotted_path;
  SkPath solid_path;

  // Draw dashed line at 50%.
  dotted_path.moveTo({0, bounds().height() / 2.0f});
  dotted_path.lineTo(
      {static_cast<SkScalar>(bounds().width()), bounds().height() / 2.0f});

  // Draw border and ticks.
  solid_path.addRect(SkRect::MakeXYWH(bounds().x(), bounds().y(),
                                      bounds().width(), bounds().height()));

  const SkScalar tick_length = 3;

  // Vertical interval ticks (drawn horizontally).
  if (vertical_ticks_interval_ > 0) {
    float tick_bottom_offset = vertical_ticks_interval_;
    while (tick_bottom_offset <= 1) {
      // Skip 50%.
      if (fabs(tick_bottom_offset - .5) > 0.01) {
        const SkScalar line_y = (1 - tick_bottom_offset) * bounds().height();
        solid_path.moveTo({0, line_y});
        solid_path.lineTo({tick_length, line_y});

        solid_path.moveTo({bounds().width() - tick_length, line_y});
        solid_path.lineTo({static_cast<SkScalar>(bounds().width()), line_y});
      }
      tick_bottom_offset += vertical_ticks_interval_;
    }
  }

  // Horizontal interval ticks (drawn vertically).
  if (horizontal_points_number_ > 0 && horizontal_ticks_interval_ > 0) {
    // Add one more tick if graph width is not a multiple of tick width.
    const int h_ticks =
        horizontal_points_number_ / horizontal_ticks_interval_ +
        (horizontal_points_number_ % horizontal_ticks_interval_ ? 1 : 0);
    // Interval between ticks in pixels.
    const SkScalar tick_per_pixels = bounds().width() /
                                     (float)horizontal_points_number_ *
                                     horizontal_ticks_interval_;
    for (int i = 1; i < h_ticks; ++i) {
      const SkScalar line_x = bounds().width() - tick_per_pixels * i;
      solid_path.moveTo({line_x, 0});
      solid_path.lineTo({line_x, tick_length});

      solid_path.moveTo({line_x, bounds().height() - tick_length});
      solid_path.lineTo({line_x, static_cast<SkScalar>(bounds().height())});
    }
  }

  cc::PaintFlags flags;
  flags.setAntiAlias(true);
  flags.setBlendMode(SkBlendMode::kSrc);
  flags.setStyle(cc::PaintFlags::kStroke_Style);
  flags.setStrokeWidth(kHUDGraphReferenceLineWidth);
  flags.setColor(color_);
  canvas->DrawPath(solid_path, flags);

  const SkScalar intervals[] = {5, 3};
  flags.setPathEffect(cc::PathEffect::MakeDash(
      intervals, sizeof(intervals) / sizeof(intervals[0]), /*phase=*/0));
  canvas->DrawPath(dotted_path, flags);
}

void ReferenceLines::SetTopLabel(float top) {
  top_ = top;
  right_top_label_->SetText(GenerateLabelText(top_, y_unit_));
  right_middle_label_->SetText(
      GenerateLabelText((top_ - bottom_) / 2, y_unit_));

  // This might trigger label resize.
  InvalidateLayout();
}

void ReferenceLines::SetBottomLabel(float bottom) {
  bottom_ = bottom;
  right_bottom_label_->SetText(GenerateLabelText(bottom_, y_unit_));
  right_middle_label_->SetText(
      GenerateLabelText((top_ - bottom_) / 2, y_unit_));

  // This might trigger label resize.
  InvalidateLayout();
}

void ReferenceLines::SetLeftLabel(float left) {
  left_ = left;
  left_bottom_label_->SetText(GenerateLabelText(left_, x_unit_));

  // This might trigger label resize.
  InvalidateLayout();
}

void ReferenceLines::SetVerticalTicksInterval(float interval) {
  interval = std::abs(interval) >= 1 ? 0 : std::abs(interval);
  if (interval == vertical_ticks_interval_)
    return;

  vertical_ticks_interval_ = interval;
}

}  // namespace hud_display
}  // namespace ash