blob: 4aba362ffceb0fe24751dcb8045bb203ec18a63f [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/media/media_notification_background.h"
#include <algorithm>
#include "ui/gfx/canvas.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/views/view.h"
namespace ash {
namespace {
constexpr int kMediaImageGradientWidth = 40;
constexpr SkColor kMediaNotificationBackgroundColor = SK_ColorWHITE;
} // namespace
MediaNotificationBackground::MediaNotificationBackground(
views::View* owner,
int top_radius,
int bottom_radius,
double artwork_max_width_pct)
: owner_(owner),
top_radius_(top_radius),
bottom_radius_(bottom_radius),
artwork_max_width_pct_(artwork_max_width_pct) {
DCHECK(owner);
}
void MediaNotificationBackground::Paint(gfx::Canvas* canvas,
views::View* view) const {
DCHECK(view);
gfx::ScopedCanvas scoped_canvas(canvas);
gfx::Rect bounds = view->GetContentsBounds();
{
// Draw a rounded rectangle which the background will be clipped to. The
// radius is provided by the notification and can change based on where in
// the list the notification is.
const SkScalar top_radius = SkIntToScalar(top_radius_);
const SkScalar bottom_radius = SkIntToScalar(bottom_radius_);
const SkScalar radii[8] = {top_radius, top_radius, top_radius,
top_radius, bottom_radius, bottom_radius,
bottom_radius, bottom_radius};
SkPath path;
path.addRoundRect(gfx::RectToSkRect(bounds), radii, SkPath::kCW_Direction);
canvas->ClipPath(path, true);
}
{
// Draw the artwork. The artwork is resized to the height of the view while
// maintaining the aspect ratio.
gfx::Rect source_bounds =
gfx::Rect(0, 0, artwork_.width(), artwork_.height());
gfx::Rect artwork_bounds = GetArtworkBounds(bounds);
canvas->DrawImageInt(
artwork_, source_bounds.x(), source_bounds.y(), source_bounds.width(),
source_bounds.height(), artwork_bounds.x(), artwork_bounds.y(),
artwork_bounds.width(), artwork_bounds.height(), false /* filter */);
}
// Draw a filled rectangle which will act as the main background of the
// notification. This may cover up some of the artwork.
canvas->FillRect(GetFilledBackgroundBounds(bounds),
kMediaNotificationBackgroundColor);
{
// Draw a gradient to fade the color background and the image together.
gfx::Rect draw_bounds = GetGradientBounds(bounds);
const SkColor transparent =
SkColorSetA(kMediaNotificationBackgroundColor, 0);
const SkColor colors[2] = {kMediaNotificationBackgroundColor, transparent};
const SkPoint points[2] = {gfx::PointToSkPoint(draw_bounds.left_center()),
gfx::PointToSkPoint(draw_bounds.right_center())};
cc::PaintFlags flags;
flags.setAntiAlias(true);
flags.setStyle(cc::PaintFlags::kFill_Style);
flags.setShader(cc::PaintShader::MakeLinearGradient(
points, colors, nullptr, 2, SkShader::kClamp_TileMode));
canvas->DrawRect(draw_bounds, flags);
}
}
void MediaNotificationBackground::UpdateArtwork(const gfx::ImageSkia& image) {
if (artwork_.BackedBySameObjectAs(image))
return;
artwork_ = image;
owner_->SchedulePaint();
}
void MediaNotificationBackground::UpdateCornerRadius(int top_radius,
int bottom_radius) {
if (top_radius_ == top_radius && bottom_radius_ == bottom_radius)
return;
top_radius_ = top_radius;
bottom_radius_ = bottom_radius;
owner_->SchedulePaint();
}
void MediaNotificationBackground::UpdateArtworkMaxWidthPct(
double max_width_pct) {
if (artwork_max_width_pct_ == max_width_pct)
return;
artwork_max_width_pct_ = max_width_pct;
owner_->SchedulePaint();
}
int MediaNotificationBackground::GetArtworkWidth(
const gfx::Size& view_size) const {
if (artwork_.isNull())
return 0;
// Calculate the aspect ratio of the image and determine what the width of the
// image should be based on that ratio and the height of the notification.
float aspect_ratio = (float)artwork_.width() / artwork_.height();
return ceil(view_size.height() * aspect_ratio);
}
int MediaNotificationBackground::GetArtworkVisibleWidth(
const gfx::Size& view_size) const {
// The artwork should only take up a maximum percentage of the notification.
return std::min(GetArtworkWidth(view_size),
(int)ceil(view_size.width() * artwork_max_width_pct_));
}
gfx::Rect MediaNotificationBackground::GetArtworkBounds(
const gfx::Rect& view_bounds) const {
int width = GetArtworkWidth(view_bounds.size());
// The artwork should be positioned on the far right hand side of the
// notification and be the same height.
return gfx::Rect(view_bounds.right() - width, 0, width, view_bounds.height());
}
gfx::Rect MediaNotificationBackground::GetFilledBackgroundBounds(
const gfx::Rect& view_bounds) const {
// The filled background should take up the full notification except the area
// taken up by the artwork.
gfx::Rect bounds = gfx::Rect(view_bounds);
bounds.Inset(0, 0, GetArtworkVisibleWidth(view_bounds.size()), 0);
return bounds;
}
gfx::Rect MediaNotificationBackground::GetGradientBounds(
const gfx::Rect& view_bounds) const {
if (artwork_.isNull())
return gfx::Rect(0, 0, 0, 0);
// The gradient should appear above the artwork on the left.
gfx::Rect filled_bounds = GetFilledBackgroundBounds(view_bounds);
return gfx::Rect(filled_bounds.right(), view_bounds.y(),
kMediaImageGradientWidth, view_bounds.height());
}
} // namespace ash