[go: nahoru, domu]

blob: b47798adf11ca3e0bd60512aeeed879049a98b0e [file] [log] [blame]
// Copyright 2020 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/ambient/ui/ambient_background_image_view.h"
#include <memory>
#include "ash/ambient/ambient_constants.h"
#include "ash/ambient/ui/ambient_info_view.h"
#include "ash/ambient/ui/ambient_shield_view.h"
#include "ash/ambient/ui/ambient_view_ids.h"
#include "ash/ambient/ui/media_string_view.h"
#include "ash/ambient/util/ambient_util.h"
#include "base/rand_util.h"
#include "ui/events/event.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/metadata/metadata_impl_macros.h"
#include "ui/views/view_class_properties.h"
namespace ash {
namespace {
// Appearance.
constexpr int kMediaStringMarginDip = 32;
// The dicretion to translate glanceable info views in the x/y coordinates. `1`
// means positive translate, `-1` negative.
int translate_x_direction = 1;
int translate_y_direction = -1;
// The current x/y translation of glanceable info views in Dip.
int current_x_translation = 0;
int current_y_translation = 0;
gfx::ImageSkia ResizeImage(const gfx::ImageSkia& image,
const gfx::Size& view_size) {
if (image.isNull())
return gfx::ImageSkia();
const double image_width = image.width();
const double image_height = image.height();
const double view_width = view_size.width();
const double view_height = view_size.height();
const double horizontal_ratio = view_width / image_width;
const double vertical_ratio = view_height / image_height;
const double image_ratio = image_height / image_width;
const double view_ratio = view_height / view_width;
// If the image and the container view has the same orientation, e.g. both
// portrait, the |scale| will make the image filled the whole view with
// possible cropping on one direction. If they are in different orientation,
// the |scale| will display the image in the view without any cropping, but
// with empty background.
const double scale = (image_ratio - 1) * (view_ratio - 1) > 0
? std::max(horizontal_ratio, vertical_ratio)
: std::min(horizontal_ratio, vertical_ratio);
const gfx::Size& resized = gfx::ScaleToCeiledSize(image.size(), scale);
return gfx::ImageSkiaOperations::CreateResizedImage(
image, skia::ImageOperations::RESIZE_BEST, resized);
}
} // namespace
AmbientBackgroundImageView::AmbientBackgroundImageView(
AmbientViewDelegate* delegate)
: delegate_(delegate) {
DCHECK(delegate_);
SetID(AmbientViewID::kAmbientBackgroundImageView);
InitLayout();
}
AmbientBackgroundImageView::~AmbientBackgroundImageView() = default;
void AmbientBackgroundImageView::OnBoundsChanged(
const gfx::Rect& previous_bounds) {
if (!GetVisible())
return;
if (width() == 0)
return;
// When bounds changes, recalculate the visibility of related image view.
UpdateRelatedImageViewVisibility();
}
void AmbientBackgroundImageView::OnViewBoundsChanged(
views::View* observed_view) {
if (observed_view == image_view_)
SetResizedImage(image_view_, image_unscaled_);
else
SetResizedImage(related_image_view_, related_image_unscaled_);
}
void AmbientBackgroundImageView::UpdateImage(
const gfx::ImageSkia& image,
const gfx::ImageSkia& related_image) {
image_unscaled_ = image;
related_image_unscaled_ = related_image;
UpdateGlanceableInfoPosition();
const bool has_change = UpdateRelatedImageViewVisibility();
// If there is no change in the visibility of related image view, call
// SetResizedImages() directly. Otherwise it will be called from
// OnViewBoundsChanged().
if (!has_change) {
SetResizedImage(image_view_, image_unscaled_);
SetResizedImage(related_image_view_, related_image_unscaled_);
}
}
void AmbientBackgroundImageView::UpdateImageDetails(
const std::u16string& details) {
ambient_info_view_->UpdateImageDetails(details);
}
const gfx::ImageSkia& AmbientBackgroundImageView::GetCurrentImage() {
return image_view_->GetImage();
}
gfx::Rect AmbientBackgroundImageView::GetImageBoundsForTesting() const {
return image_view_->GetImageBounds();
}
gfx::Rect AmbientBackgroundImageView::GetRelatedImageBoundsForTesting() const {
return related_image_view_->GetVisible()
? related_image_view_->GetImageBounds()
: gfx::Rect();
}
void AmbientBackgroundImageView::ResetRelatedImageForTesting() {
related_image_unscaled_ = gfx::ImageSkia();
UpdateRelatedImageViewVisibility();
}
void AmbientBackgroundImageView::InitLayout() {
static const views::FlexSpecification kUnboundedScaleToZero(
views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kUnbounded);
SetLayoutManager(std::make_unique<views::FillLayout>());
// Inits container for images.
image_container_ = AddChildView(std::make_unique<views::View>());
views::FlexLayout* image_layout =
image_container_->SetLayoutManager(std::make_unique<views::FlexLayout>());
image_layout->SetOrientation(views::LayoutOrientation::kHorizontal);
image_layout->SetMainAxisAlignment(views::LayoutAlignment::kCenter);
image_layout->SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
image_view_ =
image_container_->AddChildView(std::make_unique<views::ImageView>());
// Set a place holder size for Flex layout to assign bounds.
image_view_->SetPreferredSize(gfx::Size(1, 1));
image_view_->SetProperty(views::kFlexBehaviorKey, kUnboundedScaleToZero);
observed_views_.AddObservation(image_view_);
related_image_view_ =
image_container_->AddChildView(std::make_unique<views::ImageView>());
// Set a place holder size for Flex layout to assign bounds.
related_image_view_->SetPreferredSize(gfx::Size(1, 1));
related_image_view_->SetProperty(views::kFlexBehaviorKey,
kUnboundedScaleToZero);
observed_views_.AddObservation(related_image_view_);
// Set spacing between two images.
related_image_view_->SetProperty(
views::kMarginsKey, gfx::Insets(0, kMarginLeftOfRelatedImageDip, 0, 0));
AddChildView(std::make_unique<AmbientShieldView>());
ambient_info_view_ =
AddChildView(std::make_unique<AmbientInfoView>(delegate_));
gfx::Insets shadow_insets =
gfx::ShadowValue::GetMargin(ambient::util::GetTextShadowValues());
// Inits the media string view. The media string view is positioned on the
// right-top corner of the container.
views::View* media_string_view_container_ =
AddChildView(std::make_unique<views::View>());
views::BoxLayout* media_string_layout =
media_string_view_container_->SetLayoutManager(
std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
media_string_layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kStart);
media_string_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kEnd);
media_string_layout->set_inside_border_insets(
gfx::Insets(kMediaStringMarginDip + shadow_insets.top(), 0, 0,
kMediaStringMarginDip + shadow_insets.right()));
media_string_view_ = media_string_view_container_->AddChildView(
std::make_unique<MediaStringView>());
media_string_view_->SetVisible(false);
}
void AmbientBackgroundImageView::UpdateGlanceableInfoPosition() {
constexpr int kStepDP = 5;
constexpr int kMaxTranslationDip = 20;
// Move the translation point randomly one step on each x/y direction.
int x_increment = kStepDP * base::RandInt(0, 1);
int y_increment = x_increment == 0 ? kStepDP : kStepDP * base::RandInt(0, 1);
current_x_translation += translate_x_direction * x_increment;
current_y_translation += translate_y_direction * y_increment;
// If the translation point is out of bounds, reset it within bounds and
// reverse the direction.
if (current_x_translation < 0) {
translate_x_direction = 1;
current_x_translation = 0;
} else if (current_x_translation > kMaxTranslationDip) {
translate_x_direction = -1;
current_x_translation = kMaxTranslationDip;
}
if (current_y_translation > 0) {
translate_y_direction = -1;
current_y_translation = 0;
} else if (current_y_translation < -kMaxTranslationDip) {
translate_y_direction = 1;
current_y_translation = -kMaxTranslationDip;
}
gfx::Transform transform;
transform.Translate(current_x_translation, current_y_translation);
ambient_info_view_->SetTextTransform(transform);
if (media_string_view_->GetVisible()) {
gfx::Transform media_string_transform;
media_string_transform.Translate(-current_x_translation,
-current_y_translation);
media_string_view_->layer()->SetTransform(media_string_transform);
}
}
bool AmbientBackgroundImageView::UpdateRelatedImageViewVisibility() {
const bool did_show_pair = related_image_view_->GetVisible();
const bool show_pair = IsLandscapeOrientation() && HasPairedImages();
related_image_view_->SetVisible(show_pair);
return did_show_pair != show_pair;
}
void AmbientBackgroundImageView::SetResizedImage(
views::ImageView* image_view,
const gfx::ImageSkia& image_unscaled) {
if (!image_view->GetVisible())
return;
if (image_unscaled.isNull())
return;
image_view->SetImage(ResizeImage(image_unscaled, image_view->size()));
// Intend to update the image origin in image view.
// There is no bounds change or preferred size change when updating image from
// landscape to portrait when device is in portrait orientation because we
// only show one photo. Call ResetImageSize() to trigger UpdateImageOrigin().
image_view->ResetImageSize();
}
bool AmbientBackgroundImageView::IsLandscapeOrientation() const {
return width() > height();
}
bool AmbientBackgroundImageView::HasPairedImages() const {
return !image_unscaled_.isNull() && !related_image_unscaled_.isNull();
}
BEGIN_METADATA(AmbientBackgroundImageView, views::View)
END_METADATA
} // namespace ash