123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- // Copyright 2022 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/ambient/ui/ambient_animation_view.h"
- #include <algorithm>
- #include <cstdlib>
- #include <utility>
- #include "ash/ambient/ambient_view_delegate_impl.h"
- #include "ash/ambient/metrics/ambient_multi_screen_metrics_recorder.h"
- #include "ash/ambient/model/ambient_animation_attribution_provider.h"
- #include "ash/ambient/model/ambient_backend_model.h"
- #include "ash/ambient/model/ambient_photo_config.h"
- #include "ash/ambient/resources/ambient_animation_resource_constants.h"
- #include "ash/ambient/resources/ambient_animation_static_resources.h"
- #include "ash/ambient/ui/ambient_animation_attribution_transformer.h"
- #include "ash/ambient/ui/ambient_animation_background_color.h"
- #include "ash/ambient/ui/ambient_animation_player.h"
- #include "ash/ambient/ui/ambient_animation_resizer.h"
- #include "ash/ambient/ui/ambient_animation_shield_controller.h"
- #include "ash/ambient/ui/ambient_view_ids.h"
- #include "ash/ambient/ui/glanceable_info_view.h"
- #include "ash/ambient/ui/media_string_view.h"
- #include "ash/ambient/util/ambient_util.h"
- #include "ash/public/cpp/ambient/ambient_metrics.h"
- #include "ash/public/cpp/metrics_util.h"
- #include "base/bind.h"
- #include "base/check.h"
- #include "base/location.h"
- #include "base/logging.h"
- #include "base/threading/sequenced_task_runner_handle.h"
- #include "base/time/time.h"
- #include "cc/metrics/frame_sequence_tracker.h"
- #include "cc/paint/skottie_color_map.h"
- #include "cc/paint/skottie_resource_metadata.h"
- #include "cc/paint/skottie_wrapper.h"
- #include "third_party/skia/include/core/SkColor.h"
- #include "ui/base/metadata/metadata_impl_macros.h"
- #include "ui/compositor/compositor.h"
- #include "ui/gfx/color_palette.h"
- #include "ui/gfx/geometry/insets.h"
- #include "ui/gfx/geometry/outsets.h"
- #include "ui/gfx/geometry/vector2d.h"
- #include "ui/gfx/shadow_value.h"
- #include "ui/views/background.h"
- #include "ui/views/border.h"
- #include "ui/views/controls/animated_image_view.h"
- #include "ui/views/controls/image_view.h"
- #include "ui/views/layout/box_layout_view.h"
- #include "ui/views/layout/fill_layout.h"
- #include "ui/views/widget/widget.h"
- namespace ash {
- namespace {
- // How often to shift the animation slightly to prevent screen burn.
- constexpr base::TimeDelta kAnimationJitterPeriod = base::Minutes(2);
- constexpr JitterCalculator::Config kAnimationJitterConfig = {
- /*step_size=*/2,
- /*x_min_translation=*/-10,
- /*x_max_translation=*/10,
- /*y_min_translation=*/-10,
- /*y_max_translation=*/10};
- constexpr base::TimeDelta kThroughputTrackerRestartPeriod = base::Seconds(30);
- // Amount of x and y padding there should be from the top-left of the
- // AmbientAnimationView to the top-left of the weather/time content views.
- constexpr int kWeatherTimeBorderPaddingDip = 28;
- // Amount of padding from the top-right of the AmbientAnimationView's
- // bounds to the top-right of the media string content views.
- constexpr int kMediaStringPaddingDip = 28;
- constexpr int kMediaStringTextElevation = 1;
- constexpr int kTimeFontSizeDip = 32;
- // Google Grey 500 with 10% opacity.
- constexpr SkColor kDarkModeShieldColor =
- SkColorSetA(gfx::kGoogleGrey900, SK_AlphaOPAQUE / 10);
- void LogCompositorThroughput(AmbientAnimationTheme theme, int smoothness) {
- // Use VLOG instead of DVLOG since this log is performance-related and
- // developers will almost certainly only care about this log on non-debug
- // builds.
- VLOG(1) << "Compositor throughput report: smoothness=" << smoothness;
- ambient::RecordAmbientModeAnimationSmoothness(smoothness, theme);
- }
- void OnCompositorThroughputReported(
- base::TimeTicks logging_start_time,
- AmbientAnimationTheme theme,
- const cc::FrameSequenceMetrics::CustomReportData& data) {
- base::TimeDelta duration = base::TimeTicks::Now() - logging_start_time;
- float duration_sec = duration.InSecondsF();
- VLOG(1) << "Compositor throughput report: frames_expected="
- << data.frames_expected << " frames_produced=" << data.frames_produced
- << " jank_count=" << data.jank_count
- << " expected_fps=" << data.frames_expected / duration_sec
- << " actual_fps=" << data.frames_produced / duration_sec
- << " duration=" << duration;
- metrics_util::ForSmoothness(
- base::BindRepeating(&LogCompositorThroughput, theme))
- .Run(data);
- }
- // Returns the maximum possible displacement in either dimension from the
- // original unshifted position when jitter is applied.
- int GetPaddingForAnimationJitter() {
- return std::max({abs(kAnimationJitterConfig.x_min_translation),
- abs(kAnimationJitterConfig.x_max_translation),
- abs(kAnimationJitterConfig.y_min_translation),
- abs(kAnimationJitterConfig.y_max_translation)});
- }
- // When text with shadows requires X pixels of padding from the edges of its
- // bounding view, it is not always sufficient to simply create a border within
- // the view that is X pixels wide. In the event that the text's shadow extends
- // past the text in a given direction, the text's shadow ends up with X pixels
- // of padding from the edge rather than the text itself.
- //
- // This returns the amount to *subtract* from each side of a text view's border
- // such that the text ultimately has X pixels of padding from the view's edge,
- // and the shadow may extend into the padding.
- gfx::Outsets GetTextShadowCorrection(const gfx::ShadowValues& text_shadows) {
- // A positive shadow outset means the shadow extends past the text in that
- // direction. A negative shadow outset means the shadow is "behind" the text
- // in that direction. In this case, subtracting the negative outset value
- // will result in padding that is too large (X + <shadow offset>). Hence,
- // impose a "floor" of 0 pixels here.
- static constexpr gfx::Outsets kZeroOutsetsFloor;
- gfx::Outsets shadow_outsets =
- gfx::ShadowValue::GetMargin(text_shadows).ToOutsets();
- shadow_outsets.SetToMax(kZeroOutsetsFloor);
- return shadow_outsets;
- }
- // The border serves as padding between the GlanceableInfoView and its
- // parent view's bounds.
- std::unique_ptr<views::Border> CreateGlanceableInfoBorder(
- const gfx::Vector2d& jitter = gfx::Vector2d()) {
- gfx::Outsets shadow_text_correction =
- GetTextShadowCorrection(ambient::util::GetTextShadowValues(nullptr));
- int top_padding =
- kWeatherTimeBorderPaddingDip - shadow_text_correction.top() + jitter.y();
- int left_padding =
- kWeatherTimeBorderPaddingDip - shadow_text_correction.left() + jitter.x();
- DCHECK_GE(top_padding, 0);
- DCHECK_GE(left_padding, 0);
- return views::CreateEmptyBorder(
- gfx::Insets::TLBR(top_padding, left_padding, 0, 0));
- }
- // The border serves as padding between the MediaStringView and its
- // parent view's bounds.
- std::unique_ptr<views::Border> CreateMediaStringBorder(
- const gfx::Vector2d& jitter = gfx::Vector2d()) {
- gfx::Outsets shadow_text_correction = GetTextShadowCorrection(
- ambient::util::GetTextShadowValues(nullptr, kMediaStringTextElevation));
- int top_padding =
- kMediaStringPaddingDip - shadow_text_correction.top() + jitter.y();
- int right_padding =
- kMediaStringPaddingDip - shadow_text_correction.right() + jitter.x();
- DCHECK_GE(top_padding, 0);
- DCHECK_GE(right_padding, 0);
- return views::CreateEmptyBorder(
- gfx::Insets::TLBR(top_padding, 0, 0, right_padding));
- }
- } // namespace
- AmbientAnimationView::AmbientAnimationView(
- AmbientViewDelegateImpl* view_delegate,
- AmbientAnimationProgressTracker* progress_tracker,
- std::unique_ptr<const AmbientAnimationStaticResources> static_resources,
- AmbientMultiScreenMetricsRecorder* multi_screen_metrics_recorder)
- : view_delegate_(view_delegate),
- progress_tracker_(progress_tracker),
- static_resources_(std::move(static_resources)),
- animation_photo_provider_(static_resources_.get(),
- view_delegate->GetAmbientBackendModel()),
- animation_jitter_calculator_(kAnimationJitterConfig) {
- DCHECK(view_delegate_);
- SetID(AmbientViewID::kAmbientAnimationView);
- Init(multi_screen_metrics_recorder);
- }
- AmbientAnimationView::~AmbientAnimationView() = default;
- void AmbientAnimationView::Init(
- AmbientMultiScreenMetricsRecorder* multi_screen_metrics_recorder) {
- SetUseDefaultFillLayout(true);
- views::View* animation_container_view =
- AddChildView(std::make_unique<views::View>());
- animation_container_view->SetUseDefaultFillLayout(true);
- // Purely for performance reasons. Gains 3-4 fps.
- animation_container_view->SetPaintToLayer();
- // In portrait mode, the landscape animation file is currently being used. Its
- // width is scaled down to match the width of the portrait screen, and it's
- // center-aligned leaving empty space on the top and bottom of the screen. To
- // make this look less obvious to the user, make the empty space exactly match
- // the background color of the animation itself. This may be removed in the
- // future if portrait versions of the animations are made.
- animation_container_view->SetBackground(views::CreateSolidBackground(
- GetAnimationBackgroundColor(*static_resources_->GetSkottieWrapper())));
- animated_image_view_ = animation_container_view->AddChildView(
- std::make_unique<views::AnimatedImageView>());
- auto animation = std::make_unique<lottie::Animation>(
- static_resources_->GetSkottieWrapper(), cc::SkottieColorMap(),
- &animation_photo_provider_);
- animation_observer_.Observe(animation.get());
- DCHECK(multi_screen_metrics_recorder);
- multi_screen_metrics_recorder->RegisterScreen(animation.get());
- animated_image_view_->SetAnimatedImage(std::move(animation));
- animated_image_view_observer_.Observe(animated_image_view_);
- animation_attribution_provider_ =
- std::make_unique<AmbientAnimationAttributionProvider>(
- &animation_photo_provider_, animated_image_view_->animated_image());
- // SetPaintToLayer() causes a view to be painted above its non-layer-backed
- // siblings, irrespective of the order they were added in. Using an
- // intermediate layer-backed |animation_container_view| ensures the shield is
- // painted on top of the animation, while still getting performance benefits.
- auto shield_view = std::make_unique<views::View>();
- shield_view->SetID(kAmbientShieldView);
- shield_view->SetBackground(
- views::CreateSolidBackground(kDarkModeShieldColor));
- shield_view_controller_ = std::make_unique<AmbientAnimationShieldController>(
- std::move(shield_view), /*parent_view=*/animation_container_view);
- // The set of weather/time views embedded within GlanceableInfoView should
- // appear in the top-left of the the AmbientAnimationView's boundaries with
- // |kWeatherTimeBorderPaddingDip| from the top-left corner. However, the
- // weather/time components must be bottom-aligned like so:
- // +-------------------------------------------------------------------------+
- // | |
- // | +----+ +--+ |
- // | | |+---+| | |
- // | | || || | |
- // | +----++---++--+ |
- // | |
- // | |
- // | |
- // | |
- // | |
- // | |
- // | |
- // | |
- // +-------------------------------------------------------------------------+
- // As opposed to top-aligned :
- // +-------------------------------------------------------------------------+
- // | |
- // | +----++---++--+ |
- // | | || || | |
- // | | |+---+| | |
- // | +----+ +--+ |
- // | |
- // | |
- // | |
- // | |
- // | |
- // | |
- // | |
- // | |
- // +-------------------------------------------------------------------------+
- //
- // To accomplish this, a "container" view is first created that is top-aligned
- // and has no actual content. GlanceableInfoView is then added as a child of
- // the container view and bottom-aligns its contents within the container.
- glanceable_info_container_ =
- AddChildView(std::make_unique<views::BoxLayoutView>());
- glanceable_info_container_->SetOrientation(
- views::BoxLayout::Orientation::kVertical);
- glanceable_info_container_->SetMainAxisAlignment(
- views::BoxLayout::MainAxisAlignment::kStart);
- glanceable_info_container_->SetCrossAxisAlignment(
- views::BoxLayout::CrossAxisAlignment::kStart);
- glanceable_info_container_->SetBorder(CreateGlanceableInfoBorder());
- glanceable_info_container_->AddChildView(std::make_unique<GlanceableInfoView>(
- view_delegate_.get(), kTimeFontSizeDip,
- /*time_temperature_font_color=*/gfx::kGoogleGrey900));
- // Media string should appear in the top-right corner of the
- // AmbientAnimationView's bounds.
- media_string_container_ =
- AddChildView(std::make_unique<views::BoxLayoutView>());
- media_string_container_->SetOrientation(
- views::BoxLayout::Orientation::kVertical);
- media_string_container_->SetMainAxisAlignment(
- views::BoxLayout::MainAxisAlignment::kStart);
- media_string_container_->SetCrossAxisAlignment(
- views::BoxLayout::CrossAxisAlignment::kEnd);
- media_string_container_->SetBorder(CreateMediaStringBorder());
- MediaStringView* media_string_view = media_string_container_->AddChildView(
- std::make_unique<MediaStringView>(MediaStringView::Settings(
- {/*icon_light_mode_color=*/gfx::kGoogleGrey600,
- /*icon_dark_mode_color=*/gfx::kGoogleGrey500,
- /*text_light_mode_color=*/gfx::kGoogleGrey600,
- /*text_dark_mode_color=*/gfx::kGoogleGrey500,
- kMediaStringTextElevation})));
- media_string_view->SetVisible(false);
- }
- void AmbientAnimationView::AnimationCycleEnded(
- const lottie::Animation* animation) {
- view_delegate_->NotifyObserversMarkerHit(
- AmbientPhotoConfig::Marker::kUiCycleEnded);
- base::TimeTicks now = base::TimeTicks::Now();
- if (now - last_jitter_timestamp_ >= kAnimationJitterPeriod) {
- // AnimationCycleEnded() may be called while a ui "paint" operation is still
- // in progress. Changing translation properties of the UI while a paint
- // operation is in progress results in a fatal error deep in the UI stack.
- // Thus, post a task to apply jitter rather than invoking it synchronously.
- base::SequencedTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(&AmbientAnimationView::ApplyJitter,
- weak_factory_.GetWeakPtr()));
- last_jitter_timestamp_ = now;
- }
- }
- void AmbientAnimationView::OnViewBoundsChanged(View* observed_view) {
- DCHECK_EQ(observed_view, static_cast<View*>(animated_image_view_));
- gfx::Rect content_bounds = animated_image_view_->GetContentsBounds();
- DVLOG(4) << __func__ << " to " << content_bounds.ToString();
- if (content_bounds.IsEmpty())
- return;
- // By default, the |animated_image_view_| will render the animation with the
- // fixed dimensions specified in the Lottie file. To render the animation
- // at the view's full bounds, wait for the view's initial layout to happen
- // so that its proper bounds become available (they are 0x0 initially) before
- // starting the animation playback.
- gfx::Rect previous_animation_bounds = animated_image_view_->GetImageBounds();
- AmbientAnimationResizer::Resize(*animated_image_view_,
- GetPaddingForAnimationJitter());
- AmbientAnimationAttributionTransformer::TransformTextBox(
- *animated_image_view_);
- // When the device is in portrait mode, the landscape version of the
- // animation is currently being used. The tree shadow in "feel the breeze"
- // gets cut off at the top when doing this, making it look strange. UX
- // decision is to just omit the tree shadow in portrait mode. If/when
- // portrait versions of the animation are made, this logic can be removed.
- if (static_resources_->GetAmbientAnimationTheme() ==
- AmbientAnimationTheme::kFeelTheBreeze) {
- bool tree_shadow_toggled = animation_photo_provider_.ToggleStaticImageAsset(
- cc::HashSkottieResourceId(ambient::resources::kTreeShadowAssetId),
- /*enabled=*/content_bounds.width() >= content_bounds.height());
- DCHECK(tree_shadow_toggled);
- }
- DVLOG(4)
- << "View bounds available. Resized animation with native size "
- << animated_image_view_->animated_image()->GetOriginalSize().ToString()
- << " from " << previous_animation_bounds.ToString() << " to "
- << animated_image_view_->GetImageBounds().ToString();
- StartPlayingAnimation();
- if (!throughput_tracker_restart_timer_.IsRunning()) {
- RestartThroughputTracking();
- throughput_tracker_restart_timer_.Start(
- FROM_HERE, kThroughputTrackerRestartPeriod, this,
- &AmbientAnimationView::RestartThroughputTracking);
- }
- }
- void AmbientAnimationView::StartPlayingAnimation() {
- // There should only be one active AmbientAnimationPlayer at any given time,
- // otherwise multiple active players can lead to confusing simultaneous state
- // changes. So destroy the existing player first before creating a new one.
- animation_player_.reset();
- // |animated_image_view_| is owned by the base |View| class and outlives the
- // |animation_player_|, so it's safe to pass a raw ptr here.
- animation_player_ = std::make_unique<AmbientAnimationPlayer>(
- animated_image_view_, progress_tracker_.get());
- view_delegate_->NotifyObserversMarkerHit(
- AmbientPhotoConfig::Marker::kUiStartRendering);
- last_jitter_timestamp_ = base::TimeTicks::Now();
- }
- void AmbientAnimationView::RestartThroughputTracking() {
- // Stop() must be called to trigger throughput reporting.
- if (throughput_tracker_ && !throughput_tracker_->Stop()) {
- LOG(WARNING) << "Throughput will not be reported";
- }
- views::Widget* widget = GetWidget();
- DCHECK(widget);
- ui::Compositor* compositor = widget->GetCompositor();
- DCHECK(compositor);
- throughput_tracker_ = compositor->RequestNewThroughputTracker();
- throughput_tracker_->Start(
- base::BindOnce(&OnCompositorThroughputReported,
- /*logging_start_time=*/base::TimeTicks::Now(),
- static_resources_->GetAmbientAnimationTheme()));
- }
- void AmbientAnimationView::ApplyJitter() {
- gfx::Vector2d jitter = animation_jitter_calculator_.Calculate();
- DVLOG(4) << "Applying jitter to animation: " << jitter.ToString();
- // Sharing the same jitter between the animation and other peripheral content
- // keeps the spacing between features consistent.
- animated_image_view_->SetAdditionalTranslation(jitter);
- glanceable_info_container_->SetBorder(CreateGlanceableInfoBorder(jitter));
- media_string_container_->SetBorder(CreateMediaStringBorder(jitter));
- }
- BEGIN_METADATA(AmbientAnimationView, views::View)
- END_METADATA
- } // namespace ash
|