chromium/ash/rounded_display/rounded_display_gutter.cc

// Copyright 2023 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/rounded_display/rounded_display_gutter.h"

#include <algorithm>
#include <memory>
#include <utility>
#include <vector>

#include "ash/frame_sink/ui_resource.h"
#include "cc/paint/paint_flags.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkScalar.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/vector2d.h"

namespace ash {
namespace {

using RoundedCornerPosition = RoundedDisplayGutter::RoundedCorner::Position;
using RoundedCorner = RoundedDisplayGutter::RoundedCorner;

}  // namespace

RoundedCorner::RoundedCorner(Position position,
                             int radius,
                             const gfx::Point& origin)
    : position_(position),
      radius_(radius),
      bounds_in_pixels_(gfx::Rect(origin, gfx::Size(radius, radius))) {}

RoundedCorner& RoundedCorner::operator=(RoundedCorner&& other) = default;
RoundedCorner::RoundedCorner(RoundedCorner&& other) = default;

RoundedCorner::~RoundedCorner() = default;

bool RoundedDisplayGutter::RoundedCorner::DoesPaint() const {
  return radius_ > 0;
}

void RoundedDisplayGutter::RoundedCorner::Paint(gfx::Canvas* canvas) const {
  if (!DoesPaint()) {
    return;
  }

  PaintCornerHelper(canvas);
}

void RoundedDisplayGutter::RoundedCorner::PaintCornerHelper(
    gfx::Canvas* canvas) const {
  SkPath path;
  SkScalar startAngle = 0.0, sweepAngle = 0.0;
  SkScalar dx = 0.0, dy = 0.0;
  int translate_dx = 0.0, translate_dy = 0.0;

  switch (position_) {
    case RoundedCornerPosition::kUpperLeft:
      startAngle = -90;
      sweepAngle = -90;
      dx = radius_;
      dy = -radius_;
      translate_dx = 0;
      translate_dy = 0;
      break;
    case RoundedCornerPosition::kLowerLeft:
      startAngle = 90;
      sweepAngle = 90;
      dx = radius_;
      dy = radius_;
      translate_dx = 0;
      translate_dy = -radius_;
      break;
    case RoundedCornerPosition::kUpperRight:
      startAngle = 0;
      sweepAngle = -90;
      dx = radius_;
      dy = radius_;
      translate_dx = -radius_;
      translate_dy = 0;
      break;
    case RoundedCornerPosition::kLowerRight:
      startAngle = 0;
      sweepAngle = 90;
      dx = radius_;
      dy = -radius_;
      translate_dx = -radius_;
      translate_dy = -radius_;
      break;
  }

  const SkScalar oval_radius = radius_ * 2;
  SkRect oval{0, 0, oval_radius, oval_radius};

  path.addArc(oval, startAngle, sweepAngle);

  if (position_ == RoundedCornerPosition::kUpperLeft ||
      position_ == RoundedCornerPosition::kLowerLeft) {
    path.rLineTo(0, dy);
    path.rLineTo(dx, 0);
  }

  if (position_ == RoundedCornerPosition::kUpperRight ||
      position_ == RoundedCornerPosition::kLowerRight) {
    path.rLineTo(dx, 0);
    path.rLineTo(0, dy);
  }

  cc::PaintFlags flags;
  flags.setStyle(cc::PaintFlags::Style::kFill_Style);
  flags.setAntiAlias(true);
  flags.setColor(SK_ColorBLACK);

  canvas->Save();
  canvas->Translate({translate_dx, translate_dy});
  canvas->DrawPath(path, flags);
  canvas->Restore();
}

// -----------------------------------------------------------------------------
// RoundedDisplayGutter:

// static
std::unique_ptr<RoundedDisplayGutter> RoundedDisplayGutter::CreateGutter(
    std::vector<RoundedCorner>&& corners,
    bool is_overlay) {
  return std::make_unique<RoundedDisplayGutter>(std::move(corners), is_overlay);
}

RoundedDisplayGutter::RoundedDisplayGutter(std::vector<RoundedCorner>&& corners,
                                           bool is_overlay)
    : corners_(std::move(corners)), is_overlay_(is_overlay) {
  // A gutter must paint at least one rounded corner and at most four corners.
  DCHECK(corners_.size() > 0 && corners_.size() <= 4);

  // Since the corners of the gutter cannot be changed, both gutter bounds and
  // ui_source_id do not change either.
  bounds_in_pixels_ = CalculateGutterBounds();
  ui_source_id_ = CalculateUiSourceId();
  DCHECK(ui_source_id_ != kInvalidUiSourceId);
}

RoundedDisplayGutter::~RoundedDisplayGutter() = default;

UiSourceId RoundedDisplayGutter::ui_source_id() const {
  return ui_source_id_;
}

UiSourceId RoundedDisplayGutter::CalculateUiSourceId() const {
  UiSourceId ui_source_id = kInvalidUiSourceId;
  // Value of the position mask of the gutter will give a unique value for any
  // combination of RoundedDisplayCorners.
  for (const auto& corner : corners_) {
    ui_source_id |= corner.position();
  }

  return ui_source_id;
}

gfx::Rect RoundedDisplayGutter::CalculateGutterBounds() const {
  gfx::Rect gutter_bounds;

  for (const auto& corner : corners_) {
    gutter_bounds.Union(corner.bounds());
  }

  return gutter_bounds;
}

const gfx::Rect& RoundedDisplayGutter::bounds() const {
  return bounds_in_pixels_;
}

void RoundedDisplayGutter::Paint(gfx::Canvas* canvas) const {
  for (const auto& corner : corners_) {
    canvas->Save();
    const gfx::Vector2d offset =
        corner.bounds().OffsetFromOrigin() - bounds().OffsetFromOrigin();
    canvas->Translate(offset);
    corner.Paint(canvas);
    canvas->Restore();
  }
}

}  // namespace ash