123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459 |
- // 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/capture_mode/capture_label_view.h"
- #include "ash/capture_mode/capture_mode_constants.h"
- #include "ash/capture_mode/capture_mode_controller.h"
- #include "ash/capture_mode/capture_mode_session.h"
- #include "ash/capture_mode/capture_mode_util.h"
- #include "ash/capture_mode/stop_recording_button_tray.h"
- #include "ash/constants/ash_features.h"
- #include "ash/public/cpp/style/color_provider.h"
- #include "ash/resources/vector_icons/vector_icons.h"
- #include "ash/strings/grit/ash_strings.h"
- #include "ash/style/ash_color_provider.h"
- #include "ash/style/style_util.h"
- #include "ash/wm/tablet_mode/tablet_mode_controller.h"
- #include "base/bind.h"
- #include "base/i18n/number_formatting.h"
- #include "base/task/task_runner.h"
- #include "base/time/time.h"
- #include "ui/base/l10n/l10n_util.h"
- #include "ui/base/metadata/metadata_impl_macros.h"
- #include "ui/compositor/layer.h"
- #include "ui/gfx/animation/linear_animation.h"
- #include "ui/gfx/geometry/transform.h"
- #include "ui/gfx/paint_vector_icon.h"
- #include "ui/gfx/text_constants.h"
- #include "ui/views/animation/animation_builder.h"
- #include "ui/views/animation/ink_drop.h"
- #include "ui/views/background.h"
- #include "ui/views/controls/button/label_button.h"
- #include "ui/views/controls/focus_ring.h"
- #include "ui/views/controls/highlight_path_generator.h"
- #include "ui/views/controls/label.h"
- #include "ui/views/highlight_border.h"
- namespace ash {
- namespace {
- // Capture label button rounded corner radius.
- constexpr int kCaptureLabelRadius = 18;
- constexpr int kCountDownStartSeconds = 3;
- constexpr base::TimeDelta kCaptureLabelOpacityFadeoutDuration =
- base::Milliseconds(33);
- // Delay to enter number 3 to start count down.
- constexpr base::TimeDelta kStartCountDownDelay = base::Milliseconds(233);
- // The duration of the counter (e.g. "3", "2", etc.) fade in animation. The
- // counter also scales up as it fades in with the same duration.
- constexpr base::TimeDelta kCounterFadeInDuration = base::Milliseconds(250);
- // The delay we wait before we fade out the counter after it fades in with the
- // above duration.
- constexpr base::TimeDelta kCounterFadeOutDuration = base::Milliseconds(150);
- // The duration of the fade out and scale up animation of the counter when its
- // value is `kCountDownStartSeconds`.
- constexpr base::TimeDelta kStartCounterFadeOutDelay = base::Milliseconds(900);
- // Same as above but for all other counters (i.e. "2" and "1").
- constexpr base::TimeDelta kAllCountersFadeOutDelay = base::Milliseconds(850);
- // The duration of the fade out animation applied on the label widget once the
- // count down value reaches 1.
- constexpr base::TimeDelta kWidgetFadeOutDuration = base::Milliseconds(333);
- // The duration of drop to stop recording button position animation.
- constexpr base::TimeDelta kDropToStopRecordingButtonDuration =
- base::Milliseconds(500);
- // The counter starts at 80% scale as it fades in, and animates to a scale of
- // 100%.
- constexpr float kCounterInitialFadeInScale = 0.8f;
- // The counter ends at 120% when it finishes its fade out animation.
- constexpr float kCounterFinalFadeOutScale = 1.2f;
- // The label widget scales down to 80% as it fades out at the very end of the
- // count down.
- constexpr float kWidgetFinalFadeOutScale = 0.8f;
- } // namespace
- // -----------------------------------------------------------------------------
- // DropToStopRecordingButtonAnimation:
- // Defines an animation that calculates the transform of the label widget at
- // each step of the drop towards the stop recording button position animation.
- class DropToStopRecordingButtonAnimation : public gfx::LinearAnimation {
- public:
- DropToStopRecordingButtonAnimation(gfx::AnimationDelegate* delegate,
- const gfx::Point& start_position,
- const gfx::Point& target_position)
- : LinearAnimation(kDropToStopRecordingButtonDuration,
- gfx::LinearAnimation::kDefaultFrameRate,
- delegate),
- start_position_(start_position),
- target_position_(target_position) {}
- DropToStopRecordingButtonAnimation(
- const DropToStopRecordingButtonAnimation&) = delete;
- DropToStopRecordingButtonAnimation& operator=(
- const DropToStopRecordingButtonAnimation&) = delete;
- ~DropToStopRecordingButtonAnimation() override = default;
- const gfx::Transform& current_transform() const { return current_transform_; }
- // gfx::LinearAnimation:
- void AnimateToState(double state) override {
- // Note that this animation moves the widget at different speeds in X and Y.
- // This results in motion on a curve.
- const int new_x = gfx::Tween::IntValueBetween(
- gfx::Tween::CalculateValue(gfx::Tween::FAST_OUT_LINEAR_IN, state),
- start_position_.x(), target_position_.x());
- const int new_y = gfx::Tween::IntValueBetween(
- gfx::Tween::CalculateValue(gfx::Tween::ACCEL_30_DECEL_20_85, state),
- start_position_.y(), target_position_.y());
- current_transform_.MakeIdentity();
- current_transform_.Translate(gfx::Point(new_x, new_y) - start_position_);
- }
- private:
- // Note that the coordinate system of both `start_position_` and
- // `target_position_` must be the same. They can be both in screen, or both in
- // root. They're used to calculate and offset for a translation transform, so
- // it doesn't matter which coordinate system as long as they has the same.
- // The origin of the label widget at the start of this animation.
- const gfx::Point start_position_;
- // The origin of the stop recording button, which is the target position of
- // this animation.
- const gfx::Point target_position_;
- // The current value of the transform at each step of this animation which
- // will be applied on the label widget's layer.
- gfx::Transform current_transform_;
- };
- // -----------------------------------------------------------------------------
- // CaptureLabelView:
- CaptureLabelView::CaptureLabelView(
- CaptureModeSession* capture_mode_session,
- base::RepeatingClosure on_capture_button_pressed)
- : capture_mode_session_(capture_mode_session) {
- SetPaintToLayer();
- layer()->SetFillsBoundsOpaquely(false);
- auto* color_provider = AshColorProvider::Get();
- SkColor background_color = color_provider->GetBaseLayerColor(
- AshColorProvider::BaseLayerType::kTransparent80);
- SetBackground(views::CreateSolidBackground(background_color));
- layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(kCaptureLabelRadius));
- layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
- layer()->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
- SkColor text_color = color_provider->GetContentLayerColor(
- AshColorProvider::ContentLayerType::kTextColorPrimary);
- label_button_ = AddChildView(std::make_unique<views::LabelButton>(
- std::move(on_capture_button_pressed), std::u16string()));
- label_button_->SetPaintToLayer();
- label_button_->layer()->SetFillsBoundsOpaquely(false);
- label_button_->SetEnabledTextColors(text_color);
- label_button_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
- label_button_->SetNotifyEnterExitOnChild(true);
- views::InkDrop::Get(label_button_)
- ->SetMode(views::InkDropHost::InkDropMode::ON);
- StyleUtil::ConfigureInkDropAttributes(
- label_button_, StyleUtil::kBaseColor | StyleUtil::kInkDropOpacity);
- label_button_->SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
- label_ = AddChildView(std::make_unique<views::Label>(std::u16string()));
- label_->SetPaintToLayer();
- label_->layer()->SetFillsBoundsOpaquely(false);
- label_->SetEnabledColor(text_color);
- label_->SetBackgroundColor(SK_ColorTRANSPARENT);
- UpdateIconAndText();
- if (features::IsDarkLightModeEnabled()) {
- SetBorder(std::make_unique<views::HighlightBorder>(
- kCaptureLabelRadius, views::HighlightBorder::Type::kHighlightBorder2,
- /*use_light_colors=*/false));
- }
- }
- CaptureLabelView::~CaptureLabelView() = default;
- void CaptureLabelView::UpdateIconAndText() {
- CaptureModeController* controller = CaptureModeController::Get();
- const CaptureModeSource source = controller->source();
- const bool is_capturing_image = controller->type() == CaptureModeType::kImage;
- const bool in_tablet_mode = TabletModeController::Get()->InTabletMode();
- auto* color_provider = AshColorProvider::Get();
- SkColor icon_color = color_provider->GetContentLayerColor(
- AshColorProvider::ContentLayerType::kIconColorPrimary);
- gfx::ImageSkia icon;
- std::u16string text;
- switch (source) {
- case CaptureModeSource::kFullscreen:
- text = l10n_util::GetStringUTF16(
- is_capturing_image
- ? (in_tablet_mode
- ? IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_IMAGE_CAPTURE_TABLET
- : IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_IMAGE_CAPTURE_CLAMSHELL)
- : (in_tablet_mode
- ? IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_VIDEO_RECORD_TABLET
- : IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_VIDEO_RECORD_CLAMSHELL));
- break;
- case CaptureModeSource::kWindow: {
- if (in_tablet_mode) {
- text = l10n_util::GetStringUTF16(
- is_capturing_image
- ? IDS_ASH_SCREEN_CAPTURE_LABEL_WINDOW_IMAGE_CAPTURE
- : IDS_ASH_SCREEN_CAPTURE_LABEL_WINDOW_VIDEO_RECORD);
- }
- break;
- }
- case CaptureModeSource::kRegion: {
- if (!capture_mode_session_->is_selecting_region()) {
- if (CaptureModeController::Get()->user_capture_region().IsEmpty()) {
- // We're now in waiting to select a capture region phase.
- text = l10n_util::GetStringUTF16(
- is_capturing_image
- ? IDS_ASH_SCREEN_CAPTURE_LABEL_REGION_IMAGE_CAPTURE
- : IDS_ASH_SCREEN_CAPTURE_LABEL_REGION_VIDEO_RECORD);
- } else {
- // We're now in fine-tuning phase.
- icon = is_capturing_image
- ? gfx::CreateVectorIcon(kCaptureModeImageIcon, icon_color)
- : gfx::CreateVectorIcon(kCaptureModeVideoIcon, icon_color);
- text = l10n_util::GetStringUTF16(
- is_capturing_image ? IDS_ASH_SCREEN_CAPTURE_LABEL_IMAGE_CAPTURE
- : IDS_ASH_SCREEN_CAPTURE_LABEL_VIDEO_RECORD);
- }
- }
- break;
- }
- }
- if (!icon.isNull()) {
- label_->SetVisible(false);
- label_button_->SetVisible(true);
- // Update the icon only if one is not already present or it has changed to
- // reduce repainting.
- if (!label_button_->HasImage(views::Button::STATE_NORMAL) ||
- !icon.BackedBySameObjectAs(
- label_button_->GetImage(views::Button::STATE_NORMAL))) {
- label_button_->SetImage(views::Button::STATE_NORMAL, icon);
- }
- label_button_->SetText(text);
- } else if (!text.empty()) {
- label_button_->SetVisible(false);
- label_->SetVisible(true);
- label_->SetText(text);
- } else {
- label_button_->SetVisible(false);
- label_->SetVisible(false);
- }
- }
- bool CaptureLabelView::ShouldHandleEvent() {
- return label_button_->GetVisible() && !IsInCountDownAnimation();
- }
- void CaptureLabelView::StartCountDown(
- base::OnceClosure countdown_finished_callback) {
- countdown_finished_callback_ = std::move(countdown_finished_callback);
- // Depending on the visibility of |label_button_| and |label_|, decide which
- // view needs to fade out.
- ui::Layer* animation_layer = nullptr;
- if (label_button_->GetVisible())
- animation_layer = label_button_->layer();
- if (label_->GetVisible())
- animation_layer = label_->layer();
- if (animation_layer) {
- // Fade out the opacity.
- animation_layer->SetOpacity(1.f);
- views::AnimationBuilder()
- .SetPreemptionStrategy(
- ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
- .Once()
- .SetDuration(kCaptureLabelOpacityFadeoutDuration)
- .SetOpacity(animation_layer, 0.f);
- }
- base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
- FROM_HERE,
- base::BindOnce(&CaptureLabelView::FadeInAndOutCounter,
- weak_factory_.GetWeakPtr(), kCountDownStartSeconds),
- kStartCountDownDelay);
- }
- bool CaptureLabelView::IsInCountDownAnimation() const {
- return !!countdown_finished_callback_;
- }
- void CaptureLabelView::Layout() {
- gfx::Rect label_bounds = GetLocalBounds();
- label_button_->SetBoundsRect(label_bounds);
- label_bounds.ClampToCenteredSize(label_->GetPreferredSize());
- label_->SetBoundsRect(label_bounds);
- // This is necessary to update the focus ring, which is a child view of
- // |this|.
- views::View::Layout();
- }
- gfx::Size CaptureLabelView::CalculatePreferredSize() const {
- if (countdown_finished_callback_)
- return gfx::Size(kCaptureLabelRadius * 2, kCaptureLabelRadius * 2);
- const bool is_label_button_visible = label_button_->GetVisible();
- const bool is_label_visible = label_->GetVisible();
- if (!is_label_button_visible && !is_label_visible)
- return gfx::Size();
- if (is_label_button_visible) {
- DCHECK(!is_label_visible);
- return gfx::Size(
- label_button_->GetPreferredSize().width() + kCaptureLabelRadius * 2,
- kCaptureLabelRadius * 2);
- }
- DCHECK(is_label_visible && !is_label_button_visible);
- return gfx::Size(label_->GetPreferredSize().width() + kCaptureLabelRadius * 2,
- kCaptureLabelRadius * 2);
- }
- views::View* CaptureLabelView::GetView() {
- return label_button_;
- }
- std::unique_ptr<views::HighlightPathGenerator>
- CaptureLabelView::CreatePathGenerator() {
- // Regular focus rings are drawn outside the view's bounds. Since this view is
- // the same size as its widget, inset by half the focus ring thickness to
- // ensure the focus ring is drawn inside the widget bounds.
- return std::make_unique<views::RoundRectHighlightPathGenerator>(
- gfx::Insets(views::FocusRing::kDefaultHaloThickness / 2),
- kCaptureLabelRadius);
- }
- void CaptureLabelView::AnimationEnded(const gfx::Animation* animation) {
- DCHECK_EQ(drop_to_stop_button_animation_.get(), animation);
- OnCountDownAnimationFinished();
- }
- void CaptureLabelView::AnimationProgressed(const gfx::Animation* animation) {
- DCHECK_EQ(drop_to_stop_button_animation_.get(), animation);
- GetWidget()->GetLayer()->SetTransform(
- drop_to_stop_button_animation_->current_transform());
- }
- void CaptureLabelView::FadeInAndOutCounter(int counter_value) {
- if (counter_value == 0) {
- DropWidgetToStopRecordingButton();
- return;
- }
- label_->SetVisible(true);
- label_->SetText(base::FormatNumber(counter_value));
- Layout();
- // The counter should be initially fully transparent and scaled down 80%.
- ui::Layer* layer = label_->layer();
- layer->SetOpacity(0.f);
- layer->SetTransform(capture_mode_util::GetScaleTransformAboutCenter(
- layer, kCounterInitialFadeInScale));
- views::AnimationBuilder()
- .SetPreemptionStrategy(
- ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
- .OnEnded(base::BindOnce(&CaptureLabelView::FadeInAndOutCounter,
- weak_factory_.GetWeakPtr(), counter_value - 1))
- .Once()
- .SetDuration(kCounterFadeInDuration)
- .SetOpacity(layer, 1.f)
- .SetTransform(layer, gfx::Transform(), gfx::Tween::LINEAR_OUT_SLOW_IN)
- .At(counter_value == kCountDownStartSeconds ? kStartCounterFadeOutDelay
- : kAllCountersFadeOutDelay)
- .SetDuration(kCounterFadeOutDuration)
- .SetOpacity(layer, 0.f)
- .SetTransform(layer,
- capture_mode_util::GetScaleTransformAboutCenter(
- layer, kCounterFinalFadeOutScale),
- gfx::Tween::FAST_OUT_LINEAR_IN);
- }
- void CaptureLabelView::DropWidgetToStopRecordingButton() {
- auto* widget_window = GetWidget()->GetNativeWindow();
- StopRecordingButtonTray* stop_recording_button =
- capture_mode_util::GetStopRecordingButtonForRoot(
- widget_window->GetRootWindow());
- // Fall back to the fade out animation of the widget in case the button is not
- // available.
- if (!stop_recording_button) {
- FadeOutWidget();
- return;
- }
- // Temporarily show the button (without animation, i.e. don't use
- // `SetVisiblePreferred()`) in order to layout and get the position in which
- // it will be placed when we actually show it. `ShelfLayoutManager` will take
- // care of updating the layout when the visibility changes.
- stop_recording_button->SetVisible(true);
- stop_recording_button->UpdateLayout();
- const auto target_position =
- stop_recording_button->GetBoundsInScreen().origin();
- stop_recording_button->SetVisible(false);
- drop_to_stop_button_animation_ =
- std::make_unique<DropToStopRecordingButtonAnimation>(
- this, widget_window->GetBoundsInScreen().origin(), target_position);
- drop_to_stop_button_animation_->Start();
- }
- void CaptureLabelView::FadeOutWidget() {
- const auto tween = gfx::Tween::EASE_OUT_3;
- auto* widget_layer = GetWidget()->GetLayer();
- views::AnimationBuilder builder;
- builder
- .SetPreemptionStrategy(
- ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
- .OnEnded(base::BindOnce(&CaptureLabelView::OnCountDownAnimationFinished,
- weak_factory_.GetWeakPtr()))
- .Once()
- .At(kCounterFadeInDuration + kAllCountersFadeOutDelay)
- .SetDuration(kWidgetFadeOutDuration)
- .SetOpacity(widget_layer, 0.f, tween)
- .SetTransform(widget_layer,
- capture_mode_util::GetScaleTransformAboutCenter(
- widget_layer, kWidgetFinalFadeOutScale),
- tween);
- }
- void CaptureLabelView::OnCountDownAnimationFinished() {
- DCHECK(countdown_finished_callback_);
- std::move(countdown_finished_callback_).Run(); // `this` is destroyed here.
- }
- BEGIN_METADATA(CaptureLabelView, views::View)
- END_METADATA
- } // namespace ash
|