ambient_background_image_view.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/ambient/ui/ambient_background_image_view.h"
  5. #include <memory>
  6. #include "ash/ambient/ambient_constants.h"
  7. #include "ash/ambient/ui/ambient_info_view.h"
  8. #include "ash/ambient/ui/ambient_shield_view.h"
  9. #include "ash/ambient/ui/ambient_view_ids.h"
  10. #include "ash/ambient/ui/jitter_calculator.h"
  11. #include "ash/ambient/ui/media_string_view.h"
  12. #include "ash/ambient/util/ambient_util.h"
  13. #include "ash/shell.h"
  14. #include "base/no_destructor.h"
  15. #include "base/rand_util.h"
  16. #include "ui/base/metadata/metadata_impl_macros.h"
  17. #include "ui/compositor/layer.h"
  18. #include "ui/display/display.h"
  19. #include "ui/display/manager/display_manager.h"
  20. #include "ui/display/manager/managed_display_info.h"
  21. #include "ui/display/screen.h"
  22. #include "ui/events/event.h"
  23. #include "ui/gfx/geometry/insets.h"
  24. #include "ui/gfx/geometry/vector2d.h"
  25. #include "ui/gfx/image/image_skia_operations.h"
  26. #include "ui/gfx/skbitmap_operations.h"
  27. #include "ui/views/controls/image_view.h"
  28. #include "ui/views/layout/box_layout.h"
  29. #include "ui/views/layout/fill_layout.h"
  30. #include "ui/views/layout/flex_layout.h"
  31. #include "ui/views/layout/flex_layout_types.h"
  32. #include "ui/views/view_class_properties.h"
  33. namespace ash {
  34. namespace {
  35. // Appearance.
  36. constexpr int kMediaStringMarginDip = 32;
  37. gfx::ImageSkia ResizeImage(const gfx::ImageSkia& image,
  38. const gfx::Size& view_size) {
  39. if (image.isNull())
  40. return gfx::ImageSkia();
  41. const double image_width = image.width();
  42. const double image_height = image.height();
  43. const double view_width = view_size.width();
  44. const double view_height = view_size.height();
  45. const double horizontal_ratio = view_width / image_width;
  46. const double vertical_ratio = view_height / image_height;
  47. const double image_ratio = image_height / image_width;
  48. const double view_ratio = view_height / view_width;
  49. // If the image and the container view has the same orientation, e.g. both
  50. // portrait, the |scale| will make the image filled the whole view with
  51. // possible cropping on one direction. If they are in different orientation,
  52. // the |scale| will display the image in the view without any cropping, but
  53. // with empty background.
  54. const double scale = (image_ratio - 1) * (view_ratio - 1) > 0
  55. ? std::max(horizontal_ratio, vertical_ratio)
  56. : std::min(horizontal_ratio, vertical_ratio);
  57. const gfx::Size& resized = gfx::ScaleToCeiledSize(image.size(), scale);
  58. return gfx::ImageSkiaOperations::CreateResizedImage(
  59. image, skia::ImageOperations::RESIZE_BEST, resized);
  60. }
  61. gfx::ImageSkia MaybeRotateImage(const gfx::ImageSkia& image,
  62. const gfx::Size& view_size,
  63. views::Widget* widget) {
  64. if (image.isNull())
  65. return image;
  66. const double image_width = image.width();
  67. const double image_height = image.height();
  68. const double view_width = view_size.width();
  69. const double view_height = view_size.height();
  70. const double image_ratio = image_height / image_width;
  71. const double view_ratio = view_height / view_width;
  72. // Rotate the image to have the same orientation as the display.
  73. // Keep the relative orientation between the image and the display in portrait
  74. // mode.
  75. if ((image_ratio - 1) * (view_ratio - 1) < 0) {
  76. bool should_rotate = false;
  77. SkBitmapOperations::RotationAmount rotation_amount;
  78. const int64_t display_id =
  79. display::Screen::GetScreen()
  80. ->GetDisplayNearestWindow(widget->GetNativeWindow())
  81. .id();
  82. const auto active_rotation = Shell::Get()
  83. ->display_manager()
  84. ->GetDisplayInfo(display_id)
  85. .GetActiveRotation();
  86. switch (active_rotation) {
  87. case display::Display::ROTATE_90:
  88. should_rotate = true;
  89. rotation_amount = SkBitmapOperations::RotationAmount::ROTATION_270_CW;
  90. break;
  91. case display::Display::ROTATE_270:
  92. should_rotate = true;
  93. rotation_amount = SkBitmapOperations::RotationAmount::ROTATION_90_CW;
  94. break;
  95. default:
  96. NOTREACHED();
  97. break;
  98. }
  99. if (should_rotate) {
  100. return gfx::ImageSkiaOperations::CreateRotatedImage(image,
  101. rotation_amount);
  102. }
  103. }
  104. return image;
  105. }
  106. } // namespace
  107. AmbientBackgroundImageView::AmbientBackgroundImageView(
  108. AmbientViewDelegate* delegate,
  109. JitterCalculator* glanceable_info_jitter_calculator)
  110. : delegate_(delegate),
  111. glanceable_info_jitter_calculator_(glanceable_info_jitter_calculator) {
  112. DCHECK(delegate_);
  113. DCHECK(glanceable_info_jitter_calculator_);
  114. SetID(AmbientViewID::kAmbientBackgroundImageView);
  115. InitLayout();
  116. }
  117. AmbientBackgroundImageView::~AmbientBackgroundImageView() = default;
  118. void AmbientBackgroundImageView::OnBoundsChanged(
  119. const gfx::Rect& previous_bounds) {
  120. if (!GetVisible())
  121. return;
  122. if (width() == 0)
  123. return;
  124. UpdateLayout();
  125. // When bounds changes, recalculate the visibility of related image view.
  126. UpdateRelatedImageViewVisibility();
  127. UpdateImageDetails(details_, related_details_);
  128. }
  129. void AmbientBackgroundImageView::OnViewBoundsChanged(
  130. views::View* observed_view) {
  131. if (observed_view == image_view_)
  132. SetResizedImage(image_view_, image_unscaled_);
  133. else
  134. SetResizedImage(related_image_view_, related_image_unscaled_);
  135. }
  136. void AmbientBackgroundImageView::UpdateImage(
  137. const gfx::ImageSkia& image,
  138. const gfx::ImageSkia& related_image,
  139. bool is_portrait,
  140. ::ambient::TopicType type) {
  141. image_unscaled_ = image;
  142. related_image_unscaled_ = related_image;
  143. is_portrait_ = is_portrait;
  144. topic_type_ = type;
  145. UpdateGlanceableInfoPosition();
  146. const bool has_change = UpdateRelatedImageViewVisibility();
  147. // If there is no change in the visibility of related image view, call
  148. // SetResizedImages() directly. Otherwise it will be called from
  149. // OnViewBoundsChanged().
  150. if (!has_change) {
  151. SetResizedImage(image_view_, image_unscaled_);
  152. SetResizedImage(related_image_view_, related_image_unscaled_);
  153. }
  154. }
  155. void AmbientBackgroundImageView::UpdateImageDetails(
  156. const std::u16string& details,
  157. const std::u16string& related_details) {
  158. details_ = details;
  159. related_details_ = related_details;
  160. ambient_info_view_->UpdateImageDetails(
  161. details, MustShowPairs() ? related_details : std::u16string());
  162. }
  163. gfx::ImageSkia AmbientBackgroundImageView::GetCurrentImage() {
  164. return image_view_->GetImage();
  165. }
  166. gfx::Rect AmbientBackgroundImageView::GetImageBoundsInScreenForTesting() const {
  167. gfx::Rect rect = image_view_->GetImageBounds();
  168. views::View::ConvertRectToScreen(image_view_, &rect);
  169. return rect;
  170. }
  171. gfx::Rect AmbientBackgroundImageView::GetRelatedImageBoundsInScreenForTesting()
  172. const {
  173. if (!related_image_view_->GetVisible())
  174. return gfx::Rect();
  175. gfx::Rect rect = related_image_view_->GetImageBounds();
  176. views::View::ConvertRectToScreen(related_image_view_, &rect);
  177. return rect;
  178. }
  179. void AmbientBackgroundImageView::ResetRelatedImageForTesting() {
  180. related_image_unscaled_ = gfx::ImageSkia();
  181. UpdateRelatedImageViewVisibility();
  182. }
  183. void AmbientBackgroundImageView::InitLayout() {
  184. static const views::FlexSpecification kUnboundedScaleToZero(
  185. views::MinimumFlexSizeRule::kScaleToZero,
  186. views::MaximumFlexSizeRule::kUnbounded);
  187. SetLayoutManager(std::make_unique<views::FillLayout>());
  188. // Inits container for images.
  189. image_container_ = AddChildView(std::make_unique<views::View>());
  190. image_layout_ =
  191. image_container_->SetLayoutManager(std::make_unique<views::FlexLayout>());
  192. image_view_ =
  193. image_container_->AddChildView(std::make_unique<views::ImageView>());
  194. // Set a place holder size for Flex layout to assign bounds.
  195. image_view_->SetPreferredSize(gfx::Size(1, 1));
  196. image_view_->SetProperty(views::kFlexBehaviorKey, kUnboundedScaleToZero);
  197. observed_views_.AddObservation(image_view_);
  198. related_image_view_ =
  199. image_container_->AddChildView(std::make_unique<views::ImageView>());
  200. // Set a place holder size for Flex layout to assign bounds.
  201. related_image_view_->SetPreferredSize(gfx::Size(1, 1));
  202. related_image_view_->SetProperty(views::kFlexBehaviorKey,
  203. kUnboundedScaleToZero);
  204. observed_views_.AddObservation(related_image_view_);
  205. AddChildView(std::make_unique<AmbientShieldView>());
  206. ambient_info_view_ =
  207. AddChildView(std::make_unique<AmbientInfoView>(delegate_));
  208. gfx::Insets shadow_insets =
  209. gfx::ShadowValue::GetMargin(ambient::util::GetTextShadowValues(nullptr));
  210. // Inits the media string view. The media string view is positioned on the
  211. // right-top corner of the container.
  212. views::View* media_string_view_container_ =
  213. AddChildView(std::make_unique<views::View>());
  214. views::BoxLayout* media_string_layout =
  215. media_string_view_container_->SetLayoutManager(
  216. std::make_unique<views::BoxLayout>(
  217. views::BoxLayout::Orientation::kVertical));
  218. media_string_layout->set_main_axis_alignment(
  219. views::BoxLayout::MainAxisAlignment::kStart);
  220. media_string_layout->set_cross_axis_alignment(
  221. views::BoxLayout::CrossAxisAlignment::kEnd);
  222. media_string_layout->set_inside_border_insets(
  223. gfx::Insets::TLBR(kMediaStringMarginDip + shadow_insets.top(), 0, 0,
  224. kMediaStringMarginDip + shadow_insets.right()));
  225. media_string_view_ = media_string_view_container_->AddChildView(
  226. std::make_unique<MediaStringView>(MediaStringView::Settings(
  227. {/*icon_light_mode_color=*/ambient::util::GetContentLayerColor(
  228. AshColorProvider::ContentLayerType::kIconColorPrimary,
  229. /*dark_mode_enable=*/false),
  230. /*icon_dark_mode_color=*/
  231. ambient::util::GetContentLayerColor(
  232. AshColorProvider::ContentLayerType::kIconColorPrimary,
  233. /*dark_mode_enable=*/true),
  234. /*text_light_mode_color=*/
  235. ambient::util::GetContentLayerColor(
  236. AshColorProvider::ContentLayerType::kTextColorPrimary,
  237. /*dark_mode_enable=*/false),
  238. /*text_dark_mode_color=*/
  239. ambient::util::GetContentLayerColor(
  240. AshColorProvider::ContentLayerType::kTextColorPrimary,
  241. /*dark_mode_enable=*/true),
  242. /*text_shadow_elevation=*/
  243. ambient::util::kDefaultTextShadowElevation})));
  244. media_string_view_->SetVisible(false);
  245. }
  246. void AmbientBackgroundImageView::UpdateGlanceableInfoPosition() {
  247. gfx::Vector2d jitter = glanceable_info_jitter_calculator_->Calculate();
  248. gfx::Transform transform;
  249. transform.Translate(jitter);
  250. ambient_info_view_->SetTextTransform(transform);
  251. if (media_string_view_->GetVisible()) {
  252. gfx::Transform media_string_transform;
  253. media_string_transform.Translate(-jitter.x(), -jitter.y());
  254. media_string_view_->layer()->SetTransform(media_string_transform);
  255. }
  256. }
  257. void AmbientBackgroundImageView::UpdateLayout() {
  258. if (width() > height()) {
  259. image_layout_->SetOrientation(views::LayoutOrientation::kHorizontal);
  260. // Set spacing between two images.
  261. related_image_view_->SetProperty(
  262. views::kMarginsKey,
  263. gfx::Insets::TLBR(0, kMarginLeftOfRelatedImageDip, 0, 0));
  264. } else {
  265. image_layout_->SetOrientation(views::LayoutOrientation::kVertical);
  266. // Set spacing between two images.
  267. related_image_view_->SetProperty(
  268. views::kMarginsKey,
  269. gfx::Insets::TLBR(kMarginLeftOfRelatedImageDip, 0, 0, 0));
  270. }
  271. image_layout_->SetMainAxisAlignment(views::LayoutAlignment::kCenter);
  272. image_layout_->SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
  273. }
  274. bool AmbientBackgroundImageView::UpdateRelatedImageViewVisibility() {
  275. const bool did_show_pair = related_image_view_->GetVisible();
  276. const bool show_pair = MustShowPairs() && HasPairedImages();
  277. related_image_view_->SetVisible(show_pair);
  278. return did_show_pair != show_pair;
  279. }
  280. void AmbientBackgroundImageView::SetResizedImage(
  281. views::ImageView* image_view,
  282. const gfx::ImageSkia& image_unscaled) {
  283. if (!image_view->GetVisible())
  284. return;
  285. if (image_unscaled.isNull())
  286. return;
  287. gfx::ImageSkia image_rotated =
  288. topic_type_ == ::ambient::TopicType::kGeo
  289. ? MaybeRotateImage(image_unscaled, image_view->size(), GetWidget())
  290. : image_unscaled;
  291. image_view->SetImage(ResizeImage(image_rotated, image_view->size()));
  292. // Intend to update the image origin in image view.
  293. // There is no bounds change or preferred size change when updating image from
  294. // landscape to portrait when device is in portrait orientation because we
  295. // only show one photo. Call ResetImageSize() to trigger UpdateImageOrigin().
  296. image_view->ResetImageSize();
  297. }
  298. bool AmbientBackgroundImageView::MustShowPairs() const {
  299. const bool landscape_mode_portrait_image = width() > height() && is_portrait_;
  300. const bool portrait_mode_landscape_image =
  301. width() < height() && !is_portrait_;
  302. return landscape_mode_portrait_image || portrait_mode_landscape_image;
  303. }
  304. bool AmbientBackgroundImageView::HasPairedImages() const {
  305. return !image_unscaled_.isNull() && !related_image_unscaled_.isNull();
  306. }
  307. BEGIN_METADATA(AmbientBackgroundImageView, views::View)
  308. END_METADATA
  309. } // namespace ash