Skip to content

Commit

Permalink
VC UI: Add fade in and fade out animations for return to app panel
Browse files Browse the repository at this point in the history
Use layer animations for the fade in and fade out effects for the views
in return to app button.

Bug: b:267373906
Change-Id: I79bc3b9d8f3d2e79dfcae9fe6351d34214c61387
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4436056
Commit-Queue: Andre Le <leandre@chromium.org>
Reviewed-by: Alex Newcomer <newcomer@chromium.org>
Code-Coverage: Findit <findit-for-me@appspot.gserviceaccount.com>
Cr-Commit-Position: refs/heads/main@{#1132256}
  • Loading branch information
Andre Le authored and Chromium LUCI CQ committed Apr 19, 2023
1 parent 7d84228 commit 49249b5
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 0 deletions.
76 changes: 76 additions & 0 deletions ash/system/video_conference/bubble/return_to_app_panel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,22 @@
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/video_conference/bubble/bubble_view_ids.h"
#include "ash/system/video_conference/video_conference_tray_controller.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "chromeos/crosapi/mojom/video_conference.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/image_model.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/views/animation/animation_builder.h"
#include "ui/views/background.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
Expand All @@ -41,6 +45,58 @@ const int kReturnToAppIconSize = 20;

constexpr auto kPanelBoundsChangeAnimationDuration = base::Milliseconds(200);

// Performs fade in/fade out animation using `AnimationBuilder`.
void FadeInView(views::View* view, int delay_in_ms, int duration_in_ms) {
// If we are in testing with animation (non zero duration), we shouldn't have
// delays so that we can properly track when animation is completed in test.
if (ui::ScopedAnimationDurationScaleMode::duration_multiplier() ==
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION) {
delay_in_ms = 0;
}

// The view must have a layer to perform animation.
CHECK(view->layer());

views::AnimationBuilder()
.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
.Once()
.SetDuration(base::TimeDelta())
.SetOpacity(view, 0.0f)
.At(base::Milliseconds(delay_in_ms))
.SetDuration(base::Milliseconds(duration_in_ms))
.SetOpacity(view, 1.0f);
}

void FadeOutView(views::View* view,
base::WeakPtr<ReturnToAppPanel> parent_weak_ptr) {
auto on_animation_ended = base::BindOnce(
[](base::WeakPtr<ReturnToAppPanel> parent_weak_ptr, views::View* view) {
if (parent_weak_ptr) {
view->layer()->SetOpacity(1.0f);
view->SetVisible(false);
}
},
parent_weak_ptr, view);

std::pair<base::OnceClosure, base::OnceClosure> split =
base::SplitOnceCallback(std::move(on_animation_ended));

// The view must have a layer to perform animation.
CHECK(view->layer());

view->SetVisible(true);
views::AnimationBuilder()
.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
.OnEnded(std::move(split.first))
.OnAborted(std::move(split.second))
.Once()
.SetDuration(base::Milliseconds(50))
.SetVisibility(view, false)
.SetOpacity(view, 0.0f);
}

// Creates a view containing camera, microphone, and screen share icons that
// shows capturing state of a media app.
std::unique_ptr<views::View> CreateReturnToAppIconsContainer(
Expand Down Expand Up @@ -199,13 +255,23 @@ ReturnToAppButton::ReturnToAppButton(ReturnToAppPanel* panel,
expand_indicator->SetTooltipText(l10n_util::GetStringUTF16(
IDS_ASH_VIDEO_CONFERENCE_RETURN_TO_APP_SHOW_TOOLTIP));
expand_indicator_ = AddChildView(std::move(expand_indicator));

// Add a layer for icons container in the top row to perform animation.
icons_container_->SetPaintToLayer();
icons_container_->layer()->SetFillsBoundsOpaquely(false);
}

// TODO(b/253646076): Double check accessible name for this button.
SetAccessibleName(display_text);

// When we show the bubble for the first time, only the top row is visible.
SetVisible(is_top_row);

if (!is_top_row) {
// Add a layer to perform fade in animation.
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
}
}

ReturnToAppButton::~ReturnToAppButton() = default;
Expand Down Expand Up @@ -244,6 +310,10 @@ void ReturnToAppButton::OnButtonClicked(const base::UnguessableToken& id) {
expanded_ ? IDS_ASH_VIDEO_CONFERENCE_RETURN_TO_APP_HIDE_TOOLTIP
: IDS_ASH_VIDEO_CONFERENCE_RETURN_TO_APP_SHOW_TOOLTIP;
expand_indicator_->SetTooltipText(l10n_util::GetStringUTF16(tooltip_text_id));

if (icons_container_->GetVisible()) {
FadeInView(icons_container_, /*delay_in_ms=*/100, /*duration_in_ms=*/100);
}
}

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -356,6 +426,12 @@ void ReturnToAppPanel::OnExpandedStateChanged(bool expanded) {
continue;
}
child->SetVisible(expanded);

if (expanded) {
FadeInView(child, /*delay_in_ms=*/50, /*duration_in_ms=*/150);
} else {
FadeOutView(child, weak_ptr_factory_.GetWeakPtr());
}
}

// In tests, widget might be null and the animation, in some cases, might be
Expand Down
78 changes: 78 additions & 0 deletions ash/system/video_conference/bubble/return_to_app_panel_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "chromeos/crosapi/mojom/video_conference.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/test/layer_animation_stopped_waiter.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
Expand Down Expand Up @@ -443,6 +444,18 @@ TEST_F(ReturnToAppPanelTest, CollapseAnimation) {
LeftClickOn(summary_row);
EXPECT_TRUE(GetBoundsChangeAnimation()->is_animating());

// Normally, a layer animation will be performed to fade out the return to app
// buttons. However, since we are simulating different stage of the bounds
// change animation, we will set visibility right away here to prevent the
// layer animation from interfering with the bounds change animation
// simulation.
auto* first_app_row =
static_cast<ReturnToAppButton*>(return_to_app_container->children()[1]);
auto* second_app_row =
static_cast<ReturnToAppButton*>(return_to_app_container->children()[2]);
first_app_row->SetVisible(false);
second_app_row->SetVisible(false);

AnimateToValue(0.5);

auto panel_mid_animation_height = return_to_app_panel->size().height();
Expand All @@ -465,4 +478,69 @@ TEST_F(ReturnToAppPanelTest, CollapseAnimation) {
bubble_end_animation_height - bubble_mid_animation_height);
}

// Verify that the layer animations to show/hide the view are performed with
// the expected visibility and opacity before and after the animation.
TEST_F(ReturnToAppPanelTest, LayerAnimations) {
ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

controller()->ClearMediaApps();
controller()->AddMediaApp(CreateFakeMediaApp(
/*is_capturing_camera=*/true, /*is_capturing_microphone=*/false,
/*is_capturing_screen=*/false, /*title=*/u"Meet",
/*url=*/kMeetTestUrl));
controller()->AddMediaApp(CreateFakeMediaApp(
/*is_capturing_camera=*/false, /*is_capturing_microphone=*/true,
/*is_capturing_screen=*/true, /*title=*/u"Zoom",
/*url=*/""));

LeftClickOn(toggle_bubble_button());

auto* return_to_app_panel = GetReturnToAppPanel();
auto* return_to_app_container = GetReturnToAppContainer(return_to_app_panel);
auto* summary_row = static_cast<ReturnToAppButton*>(
return_to_app_container->children().front());

// Expand animation: The return to app buttons should fade in.
LeftClickOn(summary_row);

auto* first_app_row =
static_cast<ReturnToAppButton*>(return_to_app_container->children()[1]);
auto* second_app_row =
static_cast<ReturnToAppButton*>(return_to_app_container->children()[2]);

EXPECT_EQ(0, first_app_row->layer()->opacity());
EXPECT_EQ(0, second_app_row->layer()->opacity());

ui::LayerAnimationStoppedWaiter layer_animation_waiter;
layer_animation_waiter.Wait(first_app_row->layer());
layer_animation_waiter.Wait(second_app_row->layer());

EXPECT_EQ(1, first_app_row->layer()->opacity());
EXPECT_EQ(1, second_app_row->layer()->opacity());

// End the rest of the animation to test collapse animation.
SimulateAnimationEnded();
ASSERT_TRUE(summary_row->expanded());

// Collapse animation: The return to app buttons should fade out and the
// summary icons should fade in.
LeftClickOn(summary_row);
EXPECT_TRUE(GetBoundsChangeAnimation()->is_animating());

auto* summary_icons = summary_row->icons_container();
EXPECT_EQ(0, summary_icons->layer()->opacity());

EXPECT_TRUE(first_app_row->GetVisible());
EXPECT_TRUE(second_app_row->GetVisible());

layer_animation_waiter.Wait(summary_icons->layer());
layer_animation_waiter.Wait(first_app_row->layer());
layer_animation_waiter.Wait(second_app_row->layer());

EXPECT_EQ(1, summary_icons->layer()->opacity());
EXPECT_FALSE(first_app_row->GetVisible());
EXPECT_FALSE(second_app_row->GetVisible());
}

} // namespace ash::video_conference

0 comments on commit 49249b5

Please sign in to comment.