Commit e8d58900 authored by wutao's avatar wutao Committed by Commit Bot

ambient: Tile related photos in landscape

This patch shows two related photos together when device is in landscape
orientation.

Major changes:
1. Only show paired photos in landscape mode.
2. Use flex layout to dynamically add and show the secondary image.
3. If the primary image is fetched, it will be shown in the UI.
4. If the primary image is not fetched, then will ignore the secondary
   image, i.e. read from cache.
5. If the second image is fetched, it will be added to the UI if the
   screen orientation is landscape.
6. There is a 8px wide separator between two photos.
7. Only update the cache with primary image.
8. Paired photos share the same attributes.

Screenshot: https://screenshot.googleplex.com/9UirWBGxheYVqV4

Bug: b/157080996
Test: added new tests.
Change-Id: I25fb18104f317ffad629bbf7d1fcc5302806cf20
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2407176
Commit-Queue: Tao Wu <wutao@chromium.org>
Reviewed-by: default avatarXiaohui Chen <xiaohuic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#809702}
parent 4c5c54a6
...@@ -48,6 +48,10 @@ constexpr char kAmbientModeDirectoryName[] = "ambient-mode"; ...@@ -48,6 +48,10 @@ constexpr char kAmbientModeDirectoryName[] = "ambient-mode";
constexpr base::TimeDelta kTokenUsageTimeBuffer = constexpr base::TimeDelta kTokenUsageTimeBuffer =
base::TimeDelta::FromMinutes(10); base::TimeDelta::FromMinutes(10);
// PhotoView related constants.
// Spacing between two portrait images.
constexpr int kMarginLeftOfRelatedImageDip = 8;
} // namespace ash } // namespace ash
#endif // ASH_AMBIENT_AMBIENT_CONSTANTS_H_ #endif // ASH_AMBIENT_AMBIENT_CONSTANTS_H_
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "ash/public/cpp/ambient/ambient_client.h" #include "ash/public/cpp/ambient/ambient_client.h"
#include "ash/public/cpp/image_downloader.h" #include "ash/public/cpp/image_downloader.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "base/barrier_closure.h"
#include "base/base64.h" #include "base/base64.h"
#include "base/base_paths.h" #include "base/base_paths.h"
#include "base/bind.h" #include "base/bind.h"
...@@ -324,15 +325,44 @@ void AmbientPhotoController::OnScreenUpdateInfoFetched( ...@@ -324,15 +325,44 @@ void AmbientPhotoController::OnScreenUpdateInfoFetched(
StartDownloadingWeatherConditionIcon(screen_update.weather_info); StartDownloadingWeatherConditionIcon(screen_update.weather_info);
} }
void AmbientPhotoController::ResetImageData() {
image_data_.reset();
related_image_data_.reset();
image_details_.reset();
image_ = gfx::ImageSkia();
related_image_ = gfx::ImageSkia();
}
void AmbientPhotoController::FetchPhotoRawData() { void AmbientPhotoController::FetchPhotoRawData() {
const AmbientModeTopic* topic = GetNextTopic(); const AmbientModeTopic* topic = GetNextTopic();
ResetImageData();
if (topic) { if (topic) {
const int num_callbacks = (topic->related_image_url) ? 2 : 1;
auto on_done = base::BarrierClosure(
num_callbacks,
base::BindOnce(&AmbientPhotoController::OnAllPhotoRawDataAvailable,
weak_factory_.GetWeakPtr(),
/*from_downloading=*/true));
url_loader_->Download( url_loader_->Download(
topic->GetUrl(), topic->url,
base::BindOnce(&AmbientPhotoController::OnPhotoRawDataAvailable, base::BindOnce(&AmbientPhotoController::OnPhotoRawDataAvailable,
weak_factory_.GetWeakPtr(), weak_factory_.GetWeakPtr(),
/*from_downloading=*/true, /*from_downloading=*/true,
/*is_related_image=*/false, on_done,
std::make_unique<std::string>(topic->details))); std::make_unique<std::string>(topic->details)));
if (topic->related_image_url) {
url_loader_->Download(
*(topic->related_image_url),
base::BindOnce(&AmbientPhotoController::OnPhotoRawDataAvailable,
weak_factory_.GetWeakPtr(),
/*from_downloading=*/true,
/*is_related_image=*/true, on_done,
std::make_unique<std::string>(topic->details)));
}
return; return;
} }
...@@ -367,6 +397,10 @@ void AmbientPhotoController::TryReadPhotoRawData() { ...@@ -367,6 +397,10 @@ void AmbientPhotoController::TryReadPhotoRawData() {
auto photo_data = std::make_unique<std::string>(); auto photo_data = std::make_unique<std::string>();
auto photo_details = std::make_unique<std::string>(); auto photo_details = std::make_unique<std::string>();
auto on_done =
base::BindRepeating(&AmbientPhotoController::OnAllPhotoRawDataAvailable,
weak_factory_.GetWeakPtr(),
/*from_downloading=*/false);
task_runner_->PostTaskAndReply( task_runner_->PostTaskAndReply(
FROM_HERE, FROM_HERE,
base::BindOnce( base::BindOnce(
...@@ -386,14 +420,27 @@ void AmbientPhotoController::TryReadPhotoRawData() { ...@@ -386,14 +420,27 @@ void AmbientPhotoController::TryReadPhotoRawData() {
file_name, photo_data.get(), photo_details.get()), file_name, photo_data.get(), photo_details.get()),
base::BindOnce(&AmbientPhotoController::OnPhotoRawDataAvailable, base::BindOnce(&AmbientPhotoController::OnPhotoRawDataAvailable,
weak_factory_.GetWeakPtr(), /*from_downloading=*/false, weak_factory_.GetWeakPtr(), /*from_downloading=*/false,
/*is_related_image=*/false, on_done,
std::move(photo_details), std::move(photo_data))); std::move(photo_details), std::move(photo_data)));
} }
void AmbientPhotoController::OnPhotoRawDataAvailable( void AmbientPhotoController::OnPhotoRawDataAvailable(
bool from_downloading, bool from_downloading,
bool is_related_image,
base::RepeatingClosure on_done,
std::unique_ptr<std::string> details, std::unique_ptr<std::string> details,
std::unique_ptr<std::string> data) { std::unique_ptr<std::string> data) {
if (!data || data->empty()) { if (is_related_image)
related_image_data_ = std::move(data);
else {
image_data_ = std::move(data);
image_details_ = std::move(details);
}
std::move(on_done).Run();
}
void AmbientPhotoController::OnAllPhotoRawDataAvailable(bool from_downloading) {
if (!image_data_ || image_data_->empty()) {
if (from_downloading) { if (from_downloading) {
LOG(ERROR) << "Failed to download image"; LOG(ERROR) << "Failed to download image";
resume_fetch_image_backoff_.InformOfRequest(/*succeeded=*/false); resume_fetch_image_backoff_.InformOfRequest(/*succeeded=*/false);
...@@ -414,6 +461,12 @@ void AmbientPhotoController::OnPhotoRawDataAvailable( ...@@ -414,6 +461,12 @@ void AmbientPhotoController::OnPhotoRawDataAvailable(
if (cache_index_for_store_ == kMaxNumberOfCachedImages) if (cache_index_for_store_ == kMaxNumberOfCachedImages)
cache_index_for_store_ = 0; cache_index_for_store_ = 0;
const int num_callbacks = related_image_data_ ? 2 : 1;
auto on_done = base::BarrierClosure(
num_callbacks,
base::BindOnce(&AmbientPhotoController::OnAllPhotoDecoded,
weak_factory_.GetWeakPtr(), from_downloading));
task_runner_->PostTaskAndReply( task_runner_->PostTaskAndReply(
FROM_HERE, FROM_HERE,
base::BindOnce( base::BindOnce(
...@@ -425,28 +478,44 @@ void AmbientPhotoController::OnPhotoRawDataAvailable( ...@@ -425,28 +478,44 @@ void AmbientPhotoController::OnPhotoRawDataAvailable(
details); details);
} }
}, },
file_name, from_downloading, *data, *details), file_name, from_downloading, *image_data_, *image_details_),
base::BindOnce(&AmbientPhotoController::DecodePhotoRawData, base::BindOnce(&AmbientPhotoController::DecodePhotoRawData,
weak_factory_.GetWeakPtr(), from_downloading, weak_factory_.GetWeakPtr(), from_downloading,
std::move(details), std::move(data))); /*is_related_image=*/false, on_done,
std::move(image_data_)));
if (related_image_data_) {
DecodePhotoRawData(from_downloading, /*is_related_image=*/true, on_done,
std::move(related_image_data_));
}
} }
void AmbientPhotoController::DecodePhotoRawData( void AmbientPhotoController::DecodePhotoRawData(
bool from_downloading, bool from_downloading,
std::unique_ptr<std::string> details, bool is_related_image,
base::RepeatingClosure on_done,
std::unique_ptr<std::string> data) { std::unique_ptr<std::string> data) {
std::vector<uint8_t> image_bytes(data->begin(), data->end()); std::vector<uint8_t> image_bytes(data->begin(), data->end());
image_decoder_->Decode( image_decoder_->Decode(
image_bytes, base::BindOnce(&AmbientPhotoController::OnPhotoDecoded, image_bytes, base::BindOnce(&AmbientPhotoController::OnPhotoDecoded,
weak_factory_.GetWeakPtr(), from_downloading, weak_factory_.GetWeakPtr(), from_downloading,
std::move(details))); is_related_image, on_done));
} }
void AmbientPhotoController::OnPhotoDecoded( void AmbientPhotoController::OnPhotoDecoded(bool from_downloading,
bool from_downloading, bool is_related_image,
std::unique_ptr<std::string> details, base::RepeatingClosure on_done,
const gfx::ImageSkia& image) { const gfx::ImageSkia& image) {
if (image.isNull()) { if (is_related_image)
related_image_ = image;
else
image_ = image;
std::move(on_done).Run();
}
void AmbientPhotoController::OnAllPhotoDecoded(bool from_downloading) {
if (image_.isNull()) {
LOG(WARNING) << "Image is null"; LOG(WARNING) << "Image is null";
if (from_downloading) if (from_downloading)
resume_fetch_image_backoff_.InformOfRequest(/*succeeded=*/false); resume_fetch_image_backoff_.InformOfRequest(/*succeeded=*/false);
...@@ -461,8 +530,12 @@ void AmbientPhotoController::OnPhotoDecoded( ...@@ -461,8 +530,12 @@ void AmbientPhotoController::OnPhotoDecoded(
resume_fetch_image_backoff_.InformOfRequest(/*succeeded=*/true); resume_fetch_image_backoff_.InformOfRequest(/*succeeded=*/true);
PhotoWithDetails detailed_photo; PhotoWithDetails detailed_photo;
detailed_photo.photo = image; detailed_photo.photo = image_;
detailed_photo.details = *details; detailed_photo.related_photo = related_image_;
detailed_photo.details = *image_details_;
ResetImageData();
ambient_backend_model_.AddNextImage(std::move(detailed_photo)); ambient_backend_model_.AddNextImage(std::move(detailed_photo));
ScheduleRefreshImage(); ScheduleRefreshImage();
......
...@@ -118,6 +118,9 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver { ...@@ -118,6 +118,9 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
void OnScreenUpdateInfoFetched(const ash::ScreenUpdate& screen_update); void OnScreenUpdateInfoFetched(const ash::ScreenUpdate& screen_update);
// Clear temporary image data to prepare next photos.
void ResetImageData();
// Fetch photo raw data by downloading or reading from cache. // Fetch photo raw data by downloading or reading from cache.
void FetchPhotoRawData(); void FetchPhotoRawData();
...@@ -125,17 +128,25 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver { ...@@ -125,17 +128,25 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
void TryReadPhotoRawData(); void TryReadPhotoRawData();
void OnPhotoRawDataAvailable(bool from_downloading, void OnPhotoRawDataAvailable(bool from_downloading,
bool is_related_image,
base::RepeatingClosure on_done,
std::unique_ptr<std::string> details, std::unique_ptr<std::string> details,
std::unique_ptr<std::string> data); std::unique_ptr<std::string> data);
void OnAllPhotoRawDataAvailable(bool from_downloading);
void DecodePhotoRawData(bool from_downloading, void DecodePhotoRawData(bool from_downloading,
std::unique_ptr<std::string> details, bool is_related_image,
base::RepeatingClosure on_done,
std::unique_ptr<std::string> data); std::unique_ptr<std::string> data);
void OnPhotoDecoded(bool from_downloading, void OnPhotoDecoded(bool from_downloading,
std::unique_ptr<std::string> details, bool is_related_image,
base::RepeatingClosure on_done,
const gfx::ImageSkia& image); const gfx::ImageSkia& image);
void OnAllPhotoDecoded(bool from_downloading);
void StartDownloadingWeatherConditionIcon( void StartDownloadingWeatherConditionIcon(
const base::Optional<WeatherInfo>& weather_info); const base::Optional<WeatherInfo>& weather_info);
...@@ -209,6 +220,13 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver { ...@@ -209,6 +220,13 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
scoped_refptr<base::SequencedTaskRunner> task_runner_; scoped_refptr<base::SequencedTaskRunner> task_runner_;
// Temporary data store when fetching images and details.
std::unique_ptr<std::string> image_data_;
std::unique_ptr<std::string> related_image_data_;
std::unique_ptr<std::string> image_details_;
gfx::ImageSkia image_;
gfx::ImageSkia related_image_;
base::WeakPtrFactory<AmbientPhotoController> weak_factory_{this}; base::WeakPtrFactory<AmbientPhotoController> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(AmbientPhotoController); DISALLOW_COPY_AND_ASSIGN(AmbientPhotoController);
......
...@@ -127,9 +127,22 @@ ScreenUpdate ToScreenUpdate( ...@@ -127,9 +127,22 @@ ScreenUpdate ToScreenUpdate(
for (auto& backdrop_topic : backdrop_screen_update.next_topics()) { for (auto& backdrop_topic : backdrop_screen_update.next_topics()) {
AmbientModeTopic ambient_topic; AmbientModeTopic ambient_topic;
DCHECK(backdrop_topic.has_url()); DCHECK(backdrop_topic.has_url());
ambient_topic.url = backdrop_topic.url();
if (backdrop_topic.has_portrait_image_url()) if (backdrop_topic.has_portrait_image_url())
ambient_topic.portrait_image_url = backdrop_topic.portrait_image_url(); ambient_topic.url = backdrop_topic.portrait_image_url();
else
ambient_topic.url = backdrop_topic.url();
if (backdrop_topic.has_related_topic()) {
if (backdrop_topic.related_topic().has_portrait_image_url()) {
ambient_topic.related_image_url =
backdrop_topic.related_topic().portrait_image_url();
} else {
ambient_topic.related_image_url =
backdrop_topic.related_topic().url();
}
}
BuildBackdropTopicDetails(backdrop_topic, ambient_topic); BuildBackdropTopicDetails(backdrop_topic, ambient_topic);
screen_update.next_topics.emplace_back(ambient_topic); screen_update.next_topics.emplace_back(ambient_topic);
} }
......
...@@ -34,6 +34,7 @@ struct ASH_EXPORT PhotoWithDetails { ...@@ -34,6 +34,7 @@ struct ASH_EXPORT PhotoWithDetails {
bool IsNull() const; bool IsNull() const;
gfx::ImageSkia photo; gfx::ImageSkia photo;
gfx::ImageSkia related_photo;
std::string details; std::string details;
}; };
......
...@@ -6,17 +6,22 @@ ...@@ -6,17 +6,22 @@
#include <memory> #include <memory>
#include "ash/ambient/ambient_constants.h"
#include "ash/ambient/ui/glanceable_info_view.h" #include "ash/ambient/ui/glanceable_info_view.h"
#include "ash/ambient/util/ambient_util.h" #include "ash/ambient/util/ambient_util.h"
#include "ash/assistant/ui/assistant_view_ids.h" #include "ash/assistant/ui/assistant_view_ids.h"
#include "base/rand_util.h" #include "base/rand_util.h"
#include "ui/events/event.h" #include "ui/events/event.h"
#include "ui/gfx/geometry/insets.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/controls/image_view.h"
#include "ui/views/controls/label.h" #include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h" #include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_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/metadata/metadata_impl_macros.h"
#include "ui/views/view_class_properties.h"
namespace ash { namespace ash {
...@@ -39,6 +44,37 @@ int translate_y_direction = -1; ...@@ -39,6 +44,37 @@ int translate_y_direction = -1;
int current_x_translation = 0; int current_x_translation = 0;
int current_y_translation = 0; int current_y_translation = 0;
const views::FlexSpecification kUnboundedScaleToZero(
views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kUnbounded);
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 } // namespace
AmbientBackgroundImageView::AmbientBackgroundImageView( AmbientBackgroundImageView::AmbientBackgroundImageView(
...@@ -65,10 +101,43 @@ void AmbientBackgroundImageView::OnGestureEvent(ui::GestureEvent* event) { ...@@ -65,10 +101,43 @@ void AmbientBackgroundImageView::OnGestureEvent(ui::GestureEvent* event) {
} }
} }
void AmbientBackgroundImageView::UpdateImage(const gfx::ImageSkia& img) { void AmbientBackgroundImageView::OnBoundsChanged(
image_view_->SetImage(img); 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(); 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( void AmbientBackgroundImageView::UpdateImageDetails(
...@@ -80,15 +149,49 @@ const gfx::ImageSkia& AmbientBackgroundImageView::GetCurrentImage() { ...@@ -80,15 +149,49 @@ const gfx::ImageSkia& AmbientBackgroundImageView::GetCurrentImage() {
return image_view_->GetImage(); return image_view_->GetImage();
} }
gfx::Rect AmbientBackgroundImageView::GetCurrentImageBoundsForTesting() const { gfx::Rect AmbientBackgroundImageView::GetImageBoundsForTesting() const {
return image_view_->GetImageBounds(); 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() { void AmbientBackgroundImageView::InitLayout() {
SetLayoutManager(std::make_unique<views::FillLayout>()); SetLayoutManager(std::make_unique<views::FillLayout>());
// Inits the image view. This view should have the same size of the screen. // Inits container for images.
image_view_ = AddChildView(std::make_unique<views::ImageView>()); 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_.Add(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_.Add(related_image_view_);
// Set spacing between two images.
related_image_view_->SetProperty(
views::kMarginsKey, gfx::Insets(0, kMarginLeftOfRelatedImageDip, 0, 0));
gfx::Insets shadow_insets = gfx::Insets shadow_insets =
gfx::ShadowValue::GetMargin(ambient::util::GetTextShadowValues()); gfx::ShadowValue::GetMargin(ambient::util::GetTextShadowValues());
...@@ -161,6 +264,41 @@ void AmbientBackgroundImageView::UpdateGlanceableInfoPosition() { ...@@ -161,6 +264,41 @@ void AmbientBackgroundImageView::UpdateGlanceableInfoPosition() {
details_label_->layer()->SetTransform(transform); details_label_->layer()->SetTransform(transform);
} }
bool AmbientBackgroundImageView::UpdateRelatedImageViewVisibility() {
const bool did_show_pair = related_image_view_->GetVisible();
const bool show_pair = IsLandscapeOrientation() && HasPairedPortraitImages();
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::HasPairedPortraitImages() const {
const auto& primary_image = image_unscaled_;
return !primary_image.isNull() && !related_image_unscaled_.isNull() &&
primary_image.height() > primary_image.width();
}
BEGIN_METADATA(AmbientBackgroundImageView, views::View) BEGIN_METADATA(AmbientBackgroundImageView, views::View)
END_METADATA END_METADATA
......
...@@ -9,9 +9,11 @@ ...@@ -9,9 +9,11 @@
#include "ash/ambient/ui/ambient_view_delegate.h" #include "ash/ambient/ui/ambient_view_delegate.h"
#include "ash/ash_export.h" #include "ash/ash_export.h"
#include "base/scoped_observer.h"
#include "ui/views/controls/image_view.h" #include "ui/views/controls/image_view.h"
#include "ui/views/metadata/metadata_header_macros.h" #include "ui/views/metadata/metadata_header_macros.h"
#include "ui/views/view.h" #include "ui/views/view.h"
#include "ui/views/view_observer.h"
namespace views { namespace views {
class Label; class Label;
...@@ -25,7 +27,8 @@ class GlanceableInfoView; ...@@ -25,7 +27,8 @@ class GlanceableInfoView;
// A custom ImageView to display photo image and details information on ambient. // A custom ImageView to display photo image and details information on ambient.
// It also handles specific mouse/gesture events to dismiss ambient when user // It also handles specific mouse/gesture events to dismiss ambient when user
// interacts with the background photos. // interacts with the background photos.
class ASH_EXPORT AmbientBackgroundImageView : public views::View { class ASH_EXPORT AmbientBackgroundImageView : public views::View,
public views::ViewObserver {
public: public:
METADATA_HEADER(AmbientBackgroundImageView); METADATA_HEADER(AmbientBackgroundImageView);
...@@ -34,36 +37,60 @@ class ASH_EXPORT AmbientBackgroundImageView : public views::View { ...@@ -34,36 +37,60 @@ class ASH_EXPORT AmbientBackgroundImageView : public views::View {
AmbientBackgroundImageView& operator=(AmbientBackgroundImageView&) = delete; AmbientBackgroundImageView& operator=(AmbientBackgroundImageView&) = delete;
~AmbientBackgroundImageView() override; ~AmbientBackgroundImageView() override;
// views::View // views::View:
bool OnMousePressed(const ui::MouseEvent& event) override; bool OnMousePressed(const ui::MouseEvent& event) override;
void OnGestureEvent(ui::GestureEvent* event) override; void OnGestureEvent(ui::GestureEvent* event) override;
void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
// Updates the display image. // views::ViewObserver:
void UpdateImage(const gfx::ImageSkia& img); void OnViewBoundsChanged(views::View* observed_view) override;
// Updates the display images.
void UpdateImage(const gfx::ImageSkia& image,
const gfx::ImageSkia& related_image);
// Updates the details for the currently displayed image. // Updates the details for the currently displayed image.
void UpdateImageDetails(const base::string16& details); void UpdateImageDetails(const base::string16& details);
const gfx::ImageSkia& GetCurrentImage(); const gfx::ImageSkia& GetCurrentImage();
gfx::Rect GetCurrentImageBoundsForTesting() const; gfx::Rect GetImageBoundsForTesting() const;
gfx::Rect GetRelatedImageBoundsForTesting() const;
void ResetRelatedImageForTesting();
private: private:
void InitLayout(); void InitLayout();
void UpdateGlanceableInfoPosition(); void UpdateGlanceableInfoPosition();
bool UpdateRelatedImageViewVisibility();
void SetResizedImage(views::ImageView* image_view,
const gfx::ImageSkia& image_unscaled);
// Whether the device is in landscape orientation.
bool IsLandscapeOrientation() const;
bool HasPairedPortraitImages() const;
// Owned by |AmbientController| and should always outlive |this|. // Owned by |AmbientController| and should always outlive |this|.
AmbientViewDelegate* delegate_ = nullptr; AmbientViewDelegate* delegate_ = nullptr;
// View to display the current image on ambient. Owned by the view hierarchy. // View to display current image(s) on ambient. Owned by the view hierarchy.
views::View* image_container_ = nullptr;
views::ImageView* image_view_ = nullptr; views::ImageView* image_view_ = nullptr;
views::ImageView* related_image_view_ = nullptr;
// The unscaled images used for scaling and displaying in different bounds.
gfx::ImageSkia image_unscaled_;
gfx::ImageSkia related_image_unscaled_;
GlanceableInfoView* glanceable_info_view_ = nullptr; GlanceableInfoView* glanceable_info_view_ = nullptr;
// Label to show details text, i.e. attribution, to be displayed for the // Label to show details text, i.e. attribution, to be displayed for the
// current image. Owned by the view hierarchy. // current image. Owned by the view hierarchy.
views::Label* details_label_ = nullptr; views::Label* details_label_ = nullptr;
ScopedObserver<views::View, views::ViewObserver> observed_views_{this};
}; };
} // namespace ash } // namespace ash
......
...@@ -21,7 +21,6 @@ ...@@ -21,7 +21,6 @@
#include "ui/compositor/animation_throughput_reporter.h" #include "ui/compositor/animation_throughput_reporter.h"
#include "ui/compositor/layer.h" #include "ui/compositor/layer.h"
#include "ui/compositor/scoped_layer_animation_settings.h" #include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/views/controls/image_view.h" #include "ui/views/controls/image_view.h"
#include "ui/views/layout/fill_layout.h" #include "ui/views/layout/fill_layout.h"
...@@ -36,32 +35,6 @@ void ReportSmoothness(int value) { ...@@ -36,32 +35,6 @@ void ReportSmoothness(int value) {
base::UmaHistogramPercentage(kPhotoTransitionSmoothness, value); base::UmaHistogramPercentage(kPhotoTransitionSmoothness, value);
} }
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 } // namespace
...@@ -80,14 +53,6 @@ const char* PhotoView::GetClassName() const { ...@@ -80,14 +53,6 @@ const char* PhotoView::GetClassName() const {
return "PhotoView"; return "PhotoView";
} }
void PhotoView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
for (const int index : {0, 1}) {
auto image = images_unscaled_[index];
auto image_resized = ResizeImage(image, size());
image_views_[index]->UpdateImage(image_resized);
}
}
void PhotoView::OnImagesChanged() { void PhotoView::OnImagesChanged() {
// If NeedToAnimate() is true, will start transition animation and // If NeedToAnimate() is true, will start transition animation and
// UpdateImages() when animation completes. Otherwise, update images // UpdateImages() when animation completes. Otherwise, update images
...@@ -123,12 +88,11 @@ void PhotoView::Init() { ...@@ -123,12 +88,11 @@ void PhotoView::Init() {
void PhotoView::UpdateImages() { void PhotoView::UpdateImages() {
auto* model = delegate_->GetAmbientBackendModel(); auto* model = delegate_->GetAmbientBackendModel();
auto& next_image = model->GetNextImage(); auto& next_image = model->GetNextImage();
images_unscaled_[image_index_] = next_image.photo; if (next_image.photo.isNull())
if (images_unscaled_[image_index_].isNull())
return; return;
auto next_resized = ResizeImage(images_unscaled_[image_index_], size()); image_views_[image_index_]->UpdateImage(next_image.photo,
image_views_[image_index_]->UpdateImage(next_resized); next_image.related_photo);
image_views_[image_index_]->UpdateImageDetails( image_views_[image_index_]->UpdateImageDetails(
base::UTF8ToUTF16(next_image.details)); base::UTF8ToUTF16(next_image.details));
image_index_ = 1 - image_index_; image_index_ = 1 - image_index_;
...@@ -178,7 +142,7 @@ void PhotoView::OnImplicitAnimationsCompleted() { ...@@ -178,7 +142,7 @@ void PhotoView::OnImplicitAnimationsCompleted() {
bool PhotoView::NeedToAnimateTransition() const { bool PhotoView::NeedToAnimateTransition() const {
// Can do transition animation if both two images in |images_unscaled_| are // Can do transition animation if both two images in |images_unscaled_| are
// not nullptr. Check the image index 1 is enough. // not nullptr. Check the image index 1 is enough.
return !images_unscaled_[1].isNull(); return !image_views_[1]->GetCurrentImage().isNull();
} }
const gfx::ImageSkia& PhotoView::GetCurrentImagesForTesting() { const gfx::ImageSkia& PhotoView::GetCurrentImagesForTesting() {
......
...@@ -35,7 +35,6 @@ class ASH_EXPORT PhotoView : public views::View, ...@@ -35,7 +35,6 @@ class ASH_EXPORT PhotoView : public views::View,
// views::View: // views::View:
const char* GetClassName() const override; const char* GetClassName() const override;
void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
// AmbientBackendModelObserver: // AmbientBackendModelObserver:
void OnImagesChanged() override; void OnImagesChanged() override;
...@@ -47,7 +46,9 @@ class ASH_EXPORT PhotoView : public views::View, ...@@ -47,7 +46,9 @@ class ASH_EXPORT PhotoView : public views::View,
friend class AmbientAshTestBase; friend class AmbientAshTestBase;
void Init(); void Init();
void UpdateImages(); void UpdateImages();
void StartTransitionAnimation(); void StartTransitionAnimation();
// Return if can start transition animation. // Return if can start transition animation.
...@@ -62,9 +63,6 @@ class ASH_EXPORT PhotoView : public views::View, ...@@ -62,9 +63,6 @@ class ASH_EXPORT PhotoView : public views::View,
// Image containers used for animation. Owned by view hierarchy. // Image containers used for animation. Owned by view hierarchy.
AmbientBackgroundImageView* image_views_[2]{nullptr, nullptr}; AmbientBackgroundImageView* image_views_[2]{nullptr, nullptr};
// The unscaled images used for scaling and displaying in different bounds.
gfx::ImageSkia images_unscaled_[2];
// The index of |image_views_| to update the next image. // The index of |image_views_| to update the next image.
int image_index_ = 0; int image_index_ = 0;
}; };
......
This diff is collapsed.
...@@ -31,10 +31,6 @@ AmbientModeTopic& AmbientModeTopic::operator=(const AmbientModeTopic&) = ...@@ -31,10 +31,6 @@ AmbientModeTopic& AmbientModeTopic::operator=(const AmbientModeTopic&) =
AmbientModeTopic::~AmbientModeTopic() = default; AmbientModeTopic::~AmbientModeTopic() = default;
std::string AmbientModeTopic::GetUrl() const {
return portrait_image_url.value_or(url);
}
// WeatherInfo------------------------------------------------------------------ // WeatherInfo------------------------------------------------------------------
WeatherInfo::WeatherInfo() = default; WeatherInfo::WeatherInfo() = default;
......
...@@ -27,9 +27,6 @@ struct ASH_PUBLIC_EXPORT AmbientModeTopic { ...@@ -27,9 +27,6 @@ struct ASH_PUBLIC_EXPORT AmbientModeTopic {
AmbientModeTopic& operator=(const AmbientModeTopic&); AmbientModeTopic& operator=(const AmbientModeTopic&);
~AmbientModeTopic(); ~AmbientModeTopic();
// Returns a non-empty url to load the landscape or portrait image.
std::string GetUrl() const;
// Details, i.e. the attribution, to be displayed for the current photo on // Details, i.e. the attribution, to be displayed for the current photo on
// ambient. // ambient.
std::string details; std::string details;
...@@ -37,9 +34,8 @@ struct ASH_PUBLIC_EXPORT AmbientModeTopic { ...@@ -37,9 +34,8 @@ struct ASH_PUBLIC_EXPORT AmbientModeTopic {
// Image url. // Image url.
std::string url; std::string url;
// Optional for non-cropped portrait style images. The same image as in // Only support portrait image tiling in landscape orientation.
// |url| but it is not cropped and is better for portrait displaying. base::Optional<std::string> related_image_url;
base::Optional<std::string> portrait_image_url;
}; };
// WeatherInfo contains the weather information we need for rendering a // WeatherInfo contains the weather information we need for rendering a
......
...@@ -81,6 +81,7 @@ void FakeAmbientBackendControllerImpl::FetchScreenUpdateInfo( ...@@ -81,6 +81,7 @@ void FakeAmbientBackendControllerImpl::FetchScreenUpdateInfo(
ash::AmbientModeTopic topic; ash::AmbientModeTopic topic;
topic.url = kFakeUrl; topic.url = kFakeUrl;
topic.details = kFakeDetails; topic.details = kFakeDetails;
topic.related_image_url = kFakeUrl;
ash::WeatherInfo weather_info; ash::WeatherInfo weather_info;
weather_info.temp_f = .0f; weather_info.temp_f = .0f;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment