capture_label_view.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  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/capture_mode/capture_label_view.h"
  5. #include "ash/capture_mode/capture_mode_constants.h"
  6. #include "ash/capture_mode/capture_mode_controller.h"
  7. #include "ash/capture_mode/capture_mode_session.h"
  8. #include "ash/capture_mode/capture_mode_util.h"
  9. #include "ash/capture_mode/stop_recording_button_tray.h"
  10. #include "ash/constants/ash_features.h"
  11. #include "ash/public/cpp/style/color_provider.h"
  12. #include "ash/resources/vector_icons/vector_icons.h"
  13. #include "ash/strings/grit/ash_strings.h"
  14. #include "ash/style/ash_color_provider.h"
  15. #include "ash/style/style_util.h"
  16. #include "ash/wm/tablet_mode/tablet_mode_controller.h"
  17. #include "base/bind.h"
  18. #include "base/i18n/number_formatting.h"
  19. #include "base/task/task_runner.h"
  20. #include "base/time/time.h"
  21. #include "ui/base/l10n/l10n_util.h"
  22. #include "ui/base/metadata/metadata_impl_macros.h"
  23. #include "ui/compositor/layer.h"
  24. #include "ui/gfx/animation/linear_animation.h"
  25. #include "ui/gfx/geometry/transform.h"
  26. #include "ui/gfx/paint_vector_icon.h"
  27. #include "ui/gfx/text_constants.h"
  28. #include "ui/views/animation/animation_builder.h"
  29. #include "ui/views/animation/ink_drop.h"
  30. #include "ui/views/background.h"
  31. #include "ui/views/controls/button/label_button.h"
  32. #include "ui/views/controls/focus_ring.h"
  33. #include "ui/views/controls/highlight_path_generator.h"
  34. #include "ui/views/controls/label.h"
  35. #include "ui/views/highlight_border.h"
  36. namespace ash {
  37. namespace {
  38. // Capture label button rounded corner radius.
  39. constexpr int kCaptureLabelRadius = 18;
  40. constexpr int kCountDownStartSeconds = 3;
  41. constexpr base::TimeDelta kCaptureLabelOpacityFadeoutDuration =
  42. base::Milliseconds(33);
  43. // Delay to enter number 3 to start count down.
  44. constexpr base::TimeDelta kStartCountDownDelay = base::Milliseconds(233);
  45. // The duration of the counter (e.g. "3", "2", etc.) fade in animation. The
  46. // counter also scales up as it fades in with the same duration.
  47. constexpr base::TimeDelta kCounterFadeInDuration = base::Milliseconds(250);
  48. // The delay we wait before we fade out the counter after it fades in with the
  49. // above duration.
  50. constexpr base::TimeDelta kCounterFadeOutDuration = base::Milliseconds(150);
  51. // The duration of the fade out and scale up animation of the counter when its
  52. // value is `kCountDownStartSeconds`.
  53. constexpr base::TimeDelta kStartCounterFadeOutDelay = base::Milliseconds(900);
  54. // Same as above but for all other counters (i.e. "2" and "1").
  55. constexpr base::TimeDelta kAllCountersFadeOutDelay = base::Milliseconds(850);
  56. // The duration of the fade out animation applied on the label widget once the
  57. // count down value reaches 1.
  58. constexpr base::TimeDelta kWidgetFadeOutDuration = base::Milliseconds(333);
  59. // The duration of drop to stop recording button position animation.
  60. constexpr base::TimeDelta kDropToStopRecordingButtonDuration =
  61. base::Milliseconds(500);
  62. // The counter starts at 80% scale as it fades in, and animates to a scale of
  63. // 100%.
  64. constexpr float kCounterInitialFadeInScale = 0.8f;
  65. // The counter ends at 120% when it finishes its fade out animation.
  66. constexpr float kCounterFinalFadeOutScale = 1.2f;
  67. // The label widget scales down to 80% as it fades out at the very end of the
  68. // count down.
  69. constexpr float kWidgetFinalFadeOutScale = 0.8f;
  70. } // namespace
  71. // -----------------------------------------------------------------------------
  72. // DropToStopRecordingButtonAnimation:
  73. // Defines an animation that calculates the transform of the label widget at
  74. // each step of the drop towards the stop recording button position animation.
  75. class DropToStopRecordingButtonAnimation : public gfx::LinearAnimation {
  76. public:
  77. DropToStopRecordingButtonAnimation(gfx::AnimationDelegate* delegate,
  78. const gfx::Point& start_position,
  79. const gfx::Point& target_position)
  80. : LinearAnimation(kDropToStopRecordingButtonDuration,
  81. gfx::LinearAnimation::kDefaultFrameRate,
  82. delegate),
  83. start_position_(start_position),
  84. target_position_(target_position) {}
  85. DropToStopRecordingButtonAnimation(
  86. const DropToStopRecordingButtonAnimation&) = delete;
  87. DropToStopRecordingButtonAnimation& operator=(
  88. const DropToStopRecordingButtonAnimation&) = delete;
  89. ~DropToStopRecordingButtonAnimation() override = default;
  90. const gfx::Transform& current_transform() const { return current_transform_; }
  91. // gfx::LinearAnimation:
  92. void AnimateToState(double state) override {
  93. // Note that this animation moves the widget at different speeds in X and Y.
  94. // This results in motion on a curve.
  95. const int new_x = gfx::Tween::IntValueBetween(
  96. gfx::Tween::CalculateValue(gfx::Tween::FAST_OUT_LINEAR_IN, state),
  97. start_position_.x(), target_position_.x());
  98. const int new_y = gfx::Tween::IntValueBetween(
  99. gfx::Tween::CalculateValue(gfx::Tween::ACCEL_30_DECEL_20_85, state),
  100. start_position_.y(), target_position_.y());
  101. current_transform_.MakeIdentity();
  102. current_transform_.Translate(gfx::Point(new_x, new_y) - start_position_);
  103. }
  104. private:
  105. // Note that the coordinate system of both `start_position_` and
  106. // `target_position_` must be the same. They can be both in screen, or both in
  107. // root. They're used to calculate and offset for a translation transform, so
  108. // it doesn't matter which coordinate system as long as they has the same.
  109. // The origin of the label widget at the start of this animation.
  110. const gfx::Point start_position_;
  111. // The origin of the stop recording button, which is the target position of
  112. // this animation.
  113. const gfx::Point target_position_;
  114. // The current value of the transform at each step of this animation which
  115. // will be applied on the label widget's layer.
  116. gfx::Transform current_transform_;
  117. };
  118. // -----------------------------------------------------------------------------
  119. // CaptureLabelView:
  120. CaptureLabelView::CaptureLabelView(
  121. CaptureModeSession* capture_mode_session,
  122. base::RepeatingClosure on_capture_button_pressed)
  123. : capture_mode_session_(capture_mode_session) {
  124. SetPaintToLayer();
  125. layer()->SetFillsBoundsOpaquely(false);
  126. auto* color_provider = AshColorProvider::Get();
  127. SkColor background_color = color_provider->GetBaseLayerColor(
  128. AshColorProvider::BaseLayerType::kTransparent80);
  129. SetBackground(views::CreateSolidBackground(background_color));
  130. layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(kCaptureLabelRadius));
  131. layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
  132. layer()->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
  133. SkColor text_color = color_provider->GetContentLayerColor(
  134. AshColorProvider::ContentLayerType::kTextColorPrimary);
  135. label_button_ = AddChildView(std::make_unique<views::LabelButton>(
  136. std::move(on_capture_button_pressed), std::u16string()));
  137. label_button_->SetPaintToLayer();
  138. label_button_->layer()->SetFillsBoundsOpaquely(false);
  139. label_button_->SetEnabledTextColors(text_color);
  140. label_button_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
  141. label_button_->SetNotifyEnterExitOnChild(true);
  142. views::InkDrop::Get(label_button_)
  143. ->SetMode(views::InkDropHost::InkDropMode::ON);
  144. StyleUtil::ConfigureInkDropAttributes(
  145. label_button_, StyleUtil::kBaseColor | StyleUtil::kInkDropOpacity);
  146. label_button_->SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
  147. label_ = AddChildView(std::make_unique<views::Label>(std::u16string()));
  148. label_->SetPaintToLayer();
  149. label_->layer()->SetFillsBoundsOpaquely(false);
  150. label_->SetEnabledColor(text_color);
  151. label_->SetBackgroundColor(SK_ColorTRANSPARENT);
  152. UpdateIconAndText();
  153. if (features::IsDarkLightModeEnabled()) {
  154. SetBorder(std::make_unique<views::HighlightBorder>(
  155. kCaptureLabelRadius, views::HighlightBorder::Type::kHighlightBorder2,
  156. /*use_light_colors=*/false));
  157. }
  158. }
  159. CaptureLabelView::~CaptureLabelView() = default;
  160. void CaptureLabelView::UpdateIconAndText() {
  161. CaptureModeController* controller = CaptureModeController::Get();
  162. const CaptureModeSource source = controller->source();
  163. const bool is_capturing_image = controller->type() == CaptureModeType::kImage;
  164. const bool in_tablet_mode = TabletModeController::Get()->InTabletMode();
  165. auto* color_provider = AshColorProvider::Get();
  166. SkColor icon_color = color_provider->GetContentLayerColor(
  167. AshColorProvider::ContentLayerType::kIconColorPrimary);
  168. gfx::ImageSkia icon;
  169. std::u16string text;
  170. switch (source) {
  171. case CaptureModeSource::kFullscreen:
  172. text = l10n_util::GetStringUTF16(
  173. is_capturing_image
  174. ? (in_tablet_mode
  175. ? IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_IMAGE_CAPTURE_TABLET
  176. : IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_IMAGE_CAPTURE_CLAMSHELL)
  177. : (in_tablet_mode
  178. ? IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_VIDEO_RECORD_TABLET
  179. : IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_VIDEO_RECORD_CLAMSHELL));
  180. break;
  181. case CaptureModeSource::kWindow: {
  182. if (in_tablet_mode) {
  183. text = l10n_util::GetStringUTF16(
  184. is_capturing_image
  185. ? IDS_ASH_SCREEN_CAPTURE_LABEL_WINDOW_IMAGE_CAPTURE
  186. : IDS_ASH_SCREEN_CAPTURE_LABEL_WINDOW_VIDEO_RECORD);
  187. }
  188. break;
  189. }
  190. case CaptureModeSource::kRegion: {
  191. if (!capture_mode_session_->is_selecting_region()) {
  192. if (CaptureModeController::Get()->user_capture_region().IsEmpty()) {
  193. // We're now in waiting to select a capture region phase.
  194. text = l10n_util::GetStringUTF16(
  195. is_capturing_image
  196. ? IDS_ASH_SCREEN_CAPTURE_LABEL_REGION_IMAGE_CAPTURE
  197. : IDS_ASH_SCREEN_CAPTURE_LABEL_REGION_VIDEO_RECORD);
  198. } else {
  199. // We're now in fine-tuning phase.
  200. icon = is_capturing_image
  201. ? gfx::CreateVectorIcon(kCaptureModeImageIcon, icon_color)
  202. : gfx::CreateVectorIcon(kCaptureModeVideoIcon, icon_color);
  203. text = l10n_util::GetStringUTF16(
  204. is_capturing_image ? IDS_ASH_SCREEN_CAPTURE_LABEL_IMAGE_CAPTURE
  205. : IDS_ASH_SCREEN_CAPTURE_LABEL_VIDEO_RECORD);
  206. }
  207. }
  208. break;
  209. }
  210. }
  211. if (!icon.isNull()) {
  212. label_->SetVisible(false);
  213. label_button_->SetVisible(true);
  214. // Update the icon only if one is not already present or it has changed to
  215. // reduce repainting.
  216. if (!label_button_->HasImage(views::Button::STATE_NORMAL) ||
  217. !icon.BackedBySameObjectAs(
  218. label_button_->GetImage(views::Button::STATE_NORMAL))) {
  219. label_button_->SetImage(views::Button::STATE_NORMAL, icon);
  220. }
  221. label_button_->SetText(text);
  222. } else if (!text.empty()) {
  223. label_button_->SetVisible(false);
  224. label_->SetVisible(true);
  225. label_->SetText(text);
  226. } else {
  227. label_button_->SetVisible(false);
  228. label_->SetVisible(false);
  229. }
  230. }
  231. bool CaptureLabelView::ShouldHandleEvent() {
  232. return label_button_->GetVisible() && !IsInCountDownAnimation();
  233. }
  234. void CaptureLabelView::StartCountDown(
  235. base::OnceClosure countdown_finished_callback) {
  236. countdown_finished_callback_ = std::move(countdown_finished_callback);
  237. // Depending on the visibility of |label_button_| and |label_|, decide which
  238. // view needs to fade out.
  239. ui::Layer* animation_layer = nullptr;
  240. if (label_button_->GetVisible())
  241. animation_layer = label_button_->layer();
  242. if (label_->GetVisible())
  243. animation_layer = label_->layer();
  244. if (animation_layer) {
  245. // Fade out the opacity.
  246. animation_layer->SetOpacity(1.f);
  247. views::AnimationBuilder()
  248. .SetPreemptionStrategy(
  249. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  250. .Once()
  251. .SetDuration(kCaptureLabelOpacityFadeoutDuration)
  252. .SetOpacity(animation_layer, 0.f);
  253. }
  254. base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
  255. FROM_HERE,
  256. base::BindOnce(&CaptureLabelView::FadeInAndOutCounter,
  257. weak_factory_.GetWeakPtr(), kCountDownStartSeconds),
  258. kStartCountDownDelay);
  259. }
  260. bool CaptureLabelView::IsInCountDownAnimation() const {
  261. return !!countdown_finished_callback_;
  262. }
  263. void CaptureLabelView::Layout() {
  264. gfx::Rect label_bounds = GetLocalBounds();
  265. label_button_->SetBoundsRect(label_bounds);
  266. label_bounds.ClampToCenteredSize(label_->GetPreferredSize());
  267. label_->SetBoundsRect(label_bounds);
  268. // This is necessary to update the focus ring, which is a child view of
  269. // |this|.
  270. views::View::Layout();
  271. }
  272. gfx::Size CaptureLabelView::CalculatePreferredSize() const {
  273. if (countdown_finished_callback_)
  274. return gfx::Size(kCaptureLabelRadius * 2, kCaptureLabelRadius * 2);
  275. const bool is_label_button_visible = label_button_->GetVisible();
  276. const bool is_label_visible = label_->GetVisible();
  277. if (!is_label_button_visible && !is_label_visible)
  278. return gfx::Size();
  279. if (is_label_button_visible) {
  280. DCHECK(!is_label_visible);
  281. return gfx::Size(
  282. label_button_->GetPreferredSize().width() + kCaptureLabelRadius * 2,
  283. kCaptureLabelRadius * 2);
  284. }
  285. DCHECK(is_label_visible && !is_label_button_visible);
  286. return gfx::Size(label_->GetPreferredSize().width() + kCaptureLabelRadius * 2,
  287. kCaptureLabelRadius * 2);
  288. }
  289. views::View* CaptureLabelView::GetView() {
  290. return label_button_;
  291. }
  292. std::unique_ptr<views::HighlightPathGenerator>
  293. CaptureLabelView::CreatePathGenerator() {
  294. // Regular focus rings are drawn outside the view's bounds. Since this view is
  295. // the same size as its widget, inset by half the focus ring thickness to
  296. // ensure the focus ring is drawn inside the widget bounds.
  297. return std::make_unique<views::RoundRectHighlightPathGenerator>(
  298. gfx::Insets(views::FocusRing::kDefaultHaloThickness / 2),
  299. kCaptureLabelRadius);
  300. }
  301. void CaptureLabelView::AnimationEnded(const gfx::Animation* animation) {
  302. DCHECK_EQ(drop_to_stop_button_animation_.get(), animation);
  303. OnCountDownAnimationFinished();
  304. }
  305. void CaptureLabelView::AnimationProgressed(const gfx::Animation* animation) {
  306. DCHECK_EQ(drop_to_stop_button_animation_.get(), animation);
  307. GetWidget()->GetLayer()->SetTransform(
  308. drop_to_stop_button_animation_->current_transform());
  309. }
  310. void CaptureLabelView::FadeInAndOutCounter(int counter_value) {
  311. if (counter_value == 0) {
  312. DropWidgetToStopRecordingButton();
  313. return;
  314. }
  315. label_->SetVisible(true);
  316. label_->SetText(base::FormatNumber(counter_value));
  317. Layout();
  318. // The counter should be initially fully transparent and scaled down 80%.
  319. ui::Layer* layer = label_->layer();
  320. layer->SetOpacity(0.f);
  321. layer->SetTransform(capture_mode_util::GetScaleTransformAboutCenter(
  322. layer, kCounterInitialFadeInScale));
  323. views::AnimationBuilder()
  324. .SetPreemptionStrategy(
  325. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  326. .OnEnded(base::BindOnce(&CaptureLabelView::FadeInAndOutCounter,
  327. weak_factory_.GetWeakPtr(), counter_value - 1))
  328. .Once()
  329. .SetDuration(kCounterFadeInDuration)
  330. .SetOpacity(layer, 1.f)
  331. .SetTransform(layer, gfx::Transform(), gfx::Tween::LINEAR_OUT_SLOW_IN)
  332. .At(counter_value == kCountDownStartSeconds ? kStartCounterFadeOutDelay
  333. : kAllCountersFadeOutDelay)
  334. .SetDuration(kCounterFadeOutDuration)
  335. .SetOpacity(layer, 0.f)
  336. .SetTransform(layer,
  337. capture_mode_util::GetScaleTransformAboutCenter(
  338. layer, kCounterFinalFadeOutScale),
  339. gfx::Tween::FAST_OUT_LINEAR_IN);
  340. }
  341. void CaptureLabelView::DropWidgetToStopRecordingButton() {
  342. auto* widget_window = GetWidget()->GetNativeWindow();
  343. StopRecordingButtonTray* stop_recording_button =
  344. capture_mode_util::GetStopRecordingButtonForRoot(
  345. widget_window->GetRootWindow());
  346. // Fall back to the fade out animation of the widget in case the button is not
  347. // available.
  348. if (!stop_recording_button) {
  349. FadeOutWidget();
  350. return;
  351. }
  352. // Temporarily show the button (without animation, i.e. don't use
  353. // `SetVisiblePreferred()`) in order to layout and get the position in which
  354. // it will be placed when we actually show it. `ShelfLayoutManager` will take
  355. // care of updating the layout when the visibility changes.
  356. stop_recording_button->SetVisible(true);
  357. stop_recording_button->UpdateLayout();
  358. const auto target_position =
  359. stop_recording_button->GetBoundsInScreen().origin();
  360. stop_recording_button->SetVisible(false);
  361. drop_to_stop_button_animation_ =
  362. std::make_unique<DropToStopRecordingButtonAnimation>(
  363. this, widget_window->GetBoundsInScreen().origin(), target_position);
  364. drop_to_stop_button_animation_->Start();
  365. }
  366. void CaptureLabelView::FadeOutWidget() {
  367. const auto tween = gfx::Tween::EASE_OUT_3;
  368. auto* widget_layer = GetWidget()->GetLayer();
  369. views::AnimationBuilder builder;
  370. builder
  371. .SetPreemptionStrategy(
  372. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  373. .OnEnded(base::BindOnce(&CaptureLabelView::OnCountDownAnimationFinished,
  374. weak_factory_.GetWeakPtr()))
  375. .Once()
  376. .At(kCounterFadeInDuration + kAllCountersFadeOutDelay)
  377. .SetDuration(kWidgetFadeOutDuration)
  378. .SetOpacity(widget_layer, 0.f, tween)
  379. .SetTransform(widget_layer,
  380. capture_mode_util::GetScaleTransformAboutCenter(
  381. widget_layer, kWidgetFinalFadeOutScale),
  382. tween);
  383. }
  384. void CaptureLabelView::OnCountDownAnimationFinished() {
  385. DCHECK(countdown_finished_callback_);
  386. std::move(countdown_finished_callback_).Run(); // `this` is destroyed here.
  387. }
  388. BEGIN_METADATA(CaptureLabelView, views::View)
  389. END_METADATA
  390. } // namespace ash