clipboard_history_bitmap_item_view.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  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/clipboard/views/clipboard_history_bitmap_item_view.h"
  5. #include "ash/clipboard/clipboard_history_item.h"
  6. #include "ash/clipboard/clipboard_history_resource_manager.h"
  7. #include "ash/clipboard/clipboard_history_util.h"
  8. #include "ash/clipboard/views/clipboard_history_delete_button.h"
  9. #include "ash/clipboard/views/clipboard_history_view_constants.h"
  10. #include "ash/public/cpp/style/scoped_light_mode_as_default.h"
  11. #include "ash/style/ash_color_provider.h"
  12. #include "base/bind.h"
  13. #include "base/containers/contains.h"
  14. #include "base/time/time.h"
  15. #include "third_party/skia/include/core/SkBitmap.h"
  16. #include "ui/base/l10n/l10n_util.h"
  17. #include "ui/base/models/image_model.h"
  18. #include "ui/compositor/layer.h"
  19. #include "ui/compositor/layer_animation_observer.h"
  20. #include "ui/compositor/scoped_layer_animation_settings.h"
  21. #include "ui/gfx/image/image.h"
  22. #include "ui/gfx/image/image_skia.h"
  23. #include "ui/strings/grit/ui_strings.h"
  24. #include "ui/views/border.h"
  25. #include "ui/views/controls/image_view.h"
  26. #include "ui/views/layout/box_layout.h"
  27. #include "ui/views/layout/fill_layout.h"
  28. #include "ui/views/view_class_properties.h"
  29. namespace ash {
  30. namespace {
  31. // The duration of the fade out animation for transitioning the placeholder
  32. // image to rendered HTML.
  33. constexpr base::TimeDelta kFadeOutDurationMs = base::Milliseconds(60);
  34. // The duration of the fade in animation for transitioning the placeholder image
  35. // to rendered HTML.
  36. constexpr base::TimeDelta kFadeInDurationMs = base::Milliseconds(200);
  37. ////////////////////////////////////////////////////////////////////////////////
  38. // FadeImageView
  39. // An ImageView which reacts to updates from ClipboardHistoryResourceManager by
  40. // fading out the old image, and fading in the new image. Used when HTML is done
  41. // rendering. Only expected to transition once in its lifetime.
  42. class FadeImageView : public views::ImageView,
  43. public ui::ImplicitAnimationObserver,
  44. public ClipboardHistoryResourceManager::Observer {
  45. public:
  46. FadeImageView(const ClipboardHistoryItem* clipboard_history_item,
  47. const ClipboardHistoryResourceManager* resource_manager,
  48. base::RepeatingClosure update_callback)
  49. : views::ImageView(),
  50. resource_manager_(resource_manager),
  51. clipboard_history_item_(*clipboard_history_item),
  52. update_callback_(update_callback) {
  53. resource_manager_->AddObserver(this);
  54. SetImageFromModel();
  55. DCHECK(update_callback_);
  56. }
  57. FadeImageView(const FadeImageView& rhs) = delete;
  58. FadeImageView& operator=(const FadeImageView& rhs) = delete;
  59. ~FadeImageView() override {
  60. StopObservingImplicitAnimations();
  61. resource_manager_->RemoveObserver(this);
  62. }
  63. // ClipboardHistoryResourceManager::Observer:
  64. void OnCachedImageModelUpdated(
  65. const std::vector<base::UnguessableToken>& item_ids) override {
  66. if (!base::Contains(item_ids, clipboard_history_item_.id()))
  67. return;
  68. // Fade the old image out, then swap in the new image.
  69. DCHECK_EQ(FadeAnimationState::kNoFadeAnimation, animation_state_);
  70. SetPaintToLayer();
  71. animation_state_ = FadeAnimationState::kFadeOut;
  72. ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
  73. settings.SetTransitionDuration(kFadeOutDurationMs);
  74. settings.AddObserver(this);
  75. layer()->SetOpacity(0.0f);
  76. }
  77. // ui::ImplicitAnimationObserver:
  78. void OnImplicitAnimationsCompleted() override {
  79. switch (animation_state_) {
  80. case FadeAnimationState::kNoFadeAnimation:
  81. NOTREACHED();
  82. return;
  83. case FadeAnimationState::kFadeOut:
  84. DCHECK_EQ(0.0f, layer()->opacity());
  85. animation_state_ = FadeAnimationState::kFadeIn;
  86. SetImageFromModel();
  87. {
  88. ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
  89. settings.AddObserver(this);
  90. settings.SetTransitionDuration(kFadeInDurationMs);
  91. layer()->SetOpacity(1.0f);
  92. }
  93. return;
  94. case FadeAnimationState::kFadeIn:
  95. DestroyLayer();
  96. animation_state_ = FadeAnimationState::kNoFadeAnimation;
  97. return;
  98. }
  99. }
  100. void SetImageFromModel() {
  101. const gfx::ImageSkia& image =
  102. *(resource_manager_->GetImageModel(clipboard_history_item_)
  103. .GetImage()
  104. .ToImageSkia());
  105. SetImage(image);
  106. // When fading in a new image, the ImageView's image has likely changed
  107. // sizes.
  108. if (animation_state_ == FadeAnimationState::kFadeIn)
  109. update_callback_.Run();
  110. }
  111. // The different animation states possible when transitioning from one
  112. // gfx::ImageSkia to the next.
  113. enum class FadeAnimationState {
  114. kNoFadeAnimation,
  115. kFadeOut,
  116. kFadeIn,
  117. };
  118. // The current animation state.
  119. FadeAnimationState animation_state_ = FadeAnimationState::kNoFadeAnimation;
  120. // The resource manager, owned by ClipboardHistoryController.
  121. const ClipboardHistoryResourceManager* const resource_manager_;
  122. // The ClipboardHistoryItem represented by this class.
  123. const ClipboardHistoryItem clipboard_history_item_;
  124. // Used to notify of image changes.
  125. base::RepeatingClosure update_callback_;
  126. };
  127. } // namespace
  128. ////////////////////////////////////////////////////////////////////////////////
  129. // ClipboardHistoryBitmapItemView::BitmapContentsView
  130. class ClipboardHistoryBitmapItemView::BitmapContentsView
  131. : public ClipboardHistoryBitmapItemView::ContentsView {
  132. public:
  133. explicit BitmapContentsView(ClipboardHistoryBitmapItemView* container)
  134. : ContentsView(container), container_(container) {
  135. SetLayoutManager(std::make_unique<views::FillLayout>());
  136. auto image_view = BuildImageView();
  137. image_view->SetPreferredSize(
  138. gfx::Size(INT_MAX, ClipboardHistoryViews::kImageViewPreferredHeight));
  139. image_view_ = AddChildView(std::move(image_view));
  140. // `border_container_view_` should be above `image_view_`.
  141. border_container_view_ = AddChildView(std::make_unique<views::View>());
  142. border_container_view_->SetBorder(views::CreateRoundedRectBorder(
  143. ClipboardHistoryViews::kImageBorderThickness,
  144. ClipboardHistoryViews::kImageRoundedCornerRadius,
  145. gfx::kPlaceholderColor));
  146. InstallDeleteButton();
  147. }
  148. BitmapContentsView(const BitmapContentsView& rhs) = delete;
  149. BitmapContentsView& operator=(const BitmapContentsView& rhs) = delete;
  150. ~BitmapContentsView() override = default;
  151. private:
  152. // ContentsView:
  153. ClipboardHistoryDeleteButton* CreateDeleteButton() override {
  154. auto delete_button_container = std::make_unique<views::View>();
  155. auto* layout_manager = delete_button_container->SetLayoutManager(
  156. std::make_unique<views::BoxLayout>(
  157. views::BoxLayout::Orientation::kHorizontal));
  158. layout_manager->set_main_axis_alignment(
  159. views::BoxLayout::MainAxisAlignment::kEnd);
  160. layout_manager->set_cross_axis_alignment(
  161. views::BoxLayout::CrossAxisAlignment::kStart);
  162. auto delete_button =
  163. std::make_unique<ClipboardHistoryDeleteButton>(container_);
  164. delete_button->SetProperty(
  165. views::kMarginsKey,
  166. ClipboardHistoryViews::kBitmapItemDeleteButtonMargins);
  167. ClipboardHistoryDeleteButton* delete_button_ptr =
  168. delete_button_container->AddChildView(std::move(delete_button));
  169. AddChildView(std::move(delete_button_container));
  170. return delete_button_ptr;
  171. }
  172. void OnBoundsChanged(const gfx::Rect& previous_bounds) override {
  173. // Create rounded corners around the contents area through the clip path
  174. // instead of layer clip. Because we have to avoid using any layer here.
  175. // Note that the menu's container does not cut the children's layers outside
  176. // of the container's bounds. As a result, if menu items have their own
  177. // layers, the part beyond the container's bounds is still visible when the
  178. // context menu is in overflow.
  179. const SkRect local_bounds = gfx::RectToSkRect(GetContentsBounds());
  180. const SkScalar radius =
  181. SkIntToScalar(ClipboardHistoryViews::kImageRoundedCornerRadius);
  182. SetClipPath(SkPath::RRect(local_bounds, radius, radius));
  183. UpdateImageViewSize();
  184. }
  185. void OnThemeChanged() override {
  186. // Use the light mode as default because the light mode is the default mode
  187. // of the native theme which decides the context menu's background color.
  188. // TODO(andrewxu): remove this line after https://crbug.com/1143009 is
  189. // fixed.
  190. ScopedLightModeAsDefault scoped_light_mode_as_default;
  191. ContentsView::OnThemeChanged();
  192. border_container_view_->GetBorder()->set_color(
  193. AshColorProvider::Get()->GetControlsLayerColor(
  194. AshColorProvider::ControlsLayerType::kHairlineBorderColor));
  195. }
  196. std::unique_ptr<views::ImageView> BuildImageView() {
  197. const auto* clipboard_history_item = container_->clipboard_history_item();
  198. switch (container_->data_format_) {
  199. case ui::ClipboardInternalFormat::kHtml:
  200. return std::make_unique<FadeImageView>(
  201. clipboard_history_item, container_->resource_manager_,
  202. base::BindRepeating(&BitmapContentsView::UpdateImageViewSize,
  203. weak_ptr_factory_.GetWeakPtr()));
  204. case ui::ClipboardInternalFormat::kPng: {
  205. auto image_view = std::make_unique<views::ImageView>();
  206. gfx::Image image;
  207. const auto& maybe_png = clipboard_history_item->data().maybe_png();
  208. if (maybe_png.has_value()) {
  209. image = gfx::Image::CreateFrom1xPNGBytes(maybe_png.value().data(),
  210. maybe_png.value().size());
  211. } else {
  212. // If we have not yet encoded the bitmap to a PNG, just create the
  213. // gfx::Image using the available bitmap. No information is lost here.
  214. auto maybe_bitmap =
  215. clipboard_history_item->data().GetBitmapIfPngNotEncoded();
  216. DCHECK(maybe_bitmap.has_value());
  217. image = gfx::Image::CreateFrom1xBitmap(maybe_bitmap.value());
  218. }
  219. ui::ImageModel image_model = ui::ImageModel::FromImage(image);
  220. image_view->SetImage(image_model);
  221. return image_view;
  222. }
  223. default:
  224. NOTREACHED();
  225. return nullptr;
  226. }
  227. }
  228. void UpdateImageViewSize() {
  229. const gfx::Size image_size = image_view_->GetImage().size();
  230. gfx::Rect contents_bounds = GetContentsBounds();
  231. const float width_ratio =
  232. image_size.width() / float(contents_bounds.width());
  233. const float height_ratio =
  234. image_size.height() / float(contents_bounds.height());
  235. // Calculate `scaling_up_ratio` depending on the image type. A bitmap image
  236. // should fill the contents bounds while an image rendered from HTML
  237. // should meet at least one edge of the contents bounds.
  238. float scaling_up_ratio = 0.f;
  239. switch (container_->data_format_) {
  240. case ui::ClipboardInternalFormat::kPng: {
  241. scaling_up_ratio = std::fmin(width_ratio, height_ratio);
  242. break;
  243. }
  244. case ui::ClipboardInternalFormat::kHtml: {
  245. scaling_up_ratio = std::fmax(width_ratio, height_ratio);
  246. break;
  247. }
  248. default:
  249. NOTREACHED();
  250. break;
  251. }
  252. DCHECK_GT(scaling_up_ratio, 0.f);
  253. image_view_->SetImageSize(
  254. gfx::Size(image_size.width() / scaling_up_ratio,
  255. image_size.height() / scaling_up_ratio));
  256. }
  257. ClipboardHistoryBitmapItemView* const container_;
  258. views::ImageView* image_view_ = nullptr;
  259. // Helps to place a border above `image_view_`.
  260. views::View* border_container_view_ = nullptr;
  261. base::WeakPtrFactory<BitmapContentsView> weak_ptr_factory_{this};
  262. };
  263. ////////////////////////////////////////////////////////////////////////////////
  264. // ClipboardHistoryBitmapItemView
  265. ClipboardHistoryBitmapItemView::ClipboardHistoryBitmapItemView(
  266. const ClipboardHistoryItem* clipboard_history_item,
  267. const ClipboardHistoryResourceManager* resource_manager,
  268. views::MenuItemView* container)
  269. : ClipboardHistoryItemView(clipboard_history_item, container),
  270. resource_manager_(resource_manager),
  271. data_format_(*ClipboardHistoryUtil::CalculateMainFormat(
  272. clipboard_history_item->data())) {}
  273. ClipboardHistoryBitmapItemView::~ClipboardHistoryBitmapItemView() = default;
  274. const char* ClipboardHistoryBitmapItemView::GetClassName() const {
  275. return "ClipboardHistoryBitmapItemView";
  276. }
  277. std::unique_ptr<ClipboardHistoryBitmapItemView::ContentsView>
  278. ClipboardHistoryBitmapItemView::CreateContentsView() {
  279. return std::make_unique<BitmapContentsView>(this);
  280. }
  281. std::u16string ClipboardHistoryBitmapItemView::GetAccessibleName() const {
  282. switch (data_format_) {
  283. case ui::ClipboardInternalFormat::kHtml:
  284. return l10n_util::GetStringUTF16(IDS_CLIPBOARD_HISTORY_MENU_HTML_IMAGE);
  285. case ui::ClipboardInternalFormat::kPng:
  286. return l10n_util::GetStringUTF16(IDS_CLIPBOARD_HISTORY_MENU_PNG_IMAGE);
  287. default:
  288. NOTREACHED();
  289. return std::u16string();
  290. }
  291. }
  292. } // namespace ash