chromium/ash/system/power/battery_image_source.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/system/power/battery_image_source.h"

#include <algorithm>
#include <cmath>

#include "ash/constants/ash_features.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/style/ash_color_provider.h"
#include "ash/system/power/power_status.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/paint_vector_icon.h"

namespace {

// The minimum height (in dp) of the charged region of the battery icon when the
// battery is present and has a charge greater than 0.
const int kMinVisualChargeLevel = 1;

// These dimensions specify the largest possible rectangle that is fully
// encompassed by the battery icon |kBatteryIcon|. This rectangle is the area
// that is "filled" to show battery charge percentage.
constexpr gfx::RectF kDefaultFillRect = gfx::RectF(7, 6, 6, 10);

}  // namespace

namespace ash {

BatteryImageSource::BatteryImageSource(
    const PowerStatus::BatteryImageInfo& info,
    int height,
    const BatteryColors& resolved_colors)
    : gfx::CanvasImageSource(gfx::Size(height, height)),
      info_(info),
      resolved_colors_(resolved_colors) {}

BatteryImageSource::~BatteryImageSource() = default;

void BatteryImageSource::Draw(gfx::Canvas* canvas) {
  // Draw the solid outline of the battery icon.
  PaintVectorIcon(canvas, kBatteryIcon, size().height(),
                  resolved_colors_.foreground_color);

  canvas->Save();

  const float dsf = canvas->UndoDeviceScaleFactor();

  // All constants below are expressed relative to a canvas size of 20. The
  // actual canvas size (i.e. |size()|) may not be 20.
  const float kAssumedCanvasSize = 20;
  const float const_scale = dsf * size().height() / kAssumedCanvasSize;

  SkPath path;

  gfx::RectF fill_rect = kDefaultFillRect;
  fill_rect.Scale(const_scale);
  path.addRect(gfx::RectToSkRect(gfx::ToEnclosingRect(fill_rect)));
  cc::PaintFlags flags;

  SkRect icon_bounds = path.getBounds();

  // |charge_level| is a value between 0 and the visual height of the icon
  // representing the number of device pixels the battery image should be
  // shown charged. The exception is when |charge_level| is very low; in this
  // case, still draw 1 dip of charge. There are only 10 dips to indicate charge
  // level. If the level is always floor rounded (as was the historical
  // behavior) 25% charge looks very low. Similarly, always rounding normally
  // makes 75% look abnormally high. To help mitigate this, UX prefers floor
  // rounding above 50%, and normal rounding below 50%.
  const float unrounded_charge_level =
      info_.charge_percent / 100.0 * icon_bounds.height();
  float charge_level = info_.charge_percent <= 50
                           ? std::round(unrounded_charge_level)
                           : std::floor(unrounded_charge_level);

  const float min_charge_level = dsf * kMinVisualChargeLevel;
  charge_level =
      std::clamp(charge_level, min_charge_level, icon_bounds.height());

  const float charge_y = icon_bounds.bottom() - charge_level;
  gfx::RectF clip_rect(0, charge_y, size().width() * dsf,
                       size().height() * dsf);
  canvas->ClipRect(clip_rect);

  const bool use_alert_color =
      charge_level == min_charge_level && info_.alert_if_low;
  flags.setColor(use_alert_color ? resolved_colors_.alert_color
                                 : resolved_colors_.foreground_color);
  canvas->DrawPath(path, flags);

  canvas->Restore();

  if (info_.badge_outline) {
    // The outline is always a vector icon with PATH_MODE_CLEAR. This means it
    // masks out anything previously drawn to the canvas. Give it any opaque
    // color so it will properly mask the rest of the battery icon. NOTE: The
    // view which renders this canvas must paint to its own non-opaque layer,
    // otherwise this outline will show up SK_ColorBlue instead of
    // transparent.
    PaintVectorIcon(canvas, *info_.badge_outline, size().height(),
                    SK_ColorBLUE);
  }

  // Paint the badge over top of the battery, if applicable.
  if (info_.icon_badge) {
    SkColor badge_color = use_alert_color ? resolved_colors_.alert_color
                                          : resolved_colors_.badge_color;
    PaintVectorIcon(canvas, *info_.icon_badge, size().height(), badge_color);
  }
}

bool BatteryImageSource::HasRepresentationAtAllScales() const {
  return true;
}

}  // namespace ash