Commit c8b18f8b authored by Thomas Tellier's avatar Thomas Tellier Committed by Chromium LUCI CQ

[CrOS] Introduce switch between unlock and capslock icons on login screen

On the login and lock screen, the easy unlock icon and the capslock enabled icon share the same space. This CL introduces an animation that switches from one to another when both should be displayed.
The one that just got enabled is immediately shown. After 2s, a transition is done to the other icon with a fade-out and fade-in effect of 0.5s each. This other icon is shown 2s and then we transition to the first one again. This cycle continues until one of the icon should not be shown.

Bug: 1003885
Change-Id: I37711de090ba757153e0bfa0e5081c9660a8a525
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2587059Reviewed-by: default avatarIan Vollick <vollick@chromium.org>
Reviewed-by: default avatarRoman Sorokin [CET] <rsorokin@chromium.org>
Commit-Queue: Thomas Tellier <tellier@google.com>
Cr-Commit-Position: refs/heads/master@{#840184}
parent f64b910a
This diff is collapsed.
...@@ -68,10 +68,8 @@ class ASH_EXPORT LoginPasswordView : public views::View, ...@@ -68,10 +68,8 @@ class ASH_EXPORT LoginPasswordView : public views::View,
views::View* submit_button() const; views::View* submit_button() const;
views::ToggleImageButton* display_password_button() const; views::ToggleImageButton* display_password_button() const;
views::View* easy_unlock_icon() const; views::View* easy_unlock_icon() const;
views::View* capslock_icon() const;
void set_immediately_hover_easy_unlock_icon(); void set_immediately_hover_easy_unlock_icon();
// Sets the timers that are used to clear and hide the password.
void SetTimers(std::unique_ptr<base::RetainingOneShotTimer> clear_timer,
std::unique_ptr<base::RetainingOneShotTimer> hide_timer);
private: private:
LoginPasswordView* view_; LoginPasswordView* view_;
...@@ -155,6 +153,8 @@ class ASH_EXPORT LoginPasswordView : public views::View, ...@@ -155,6 +153,8 @@ class ASH_EXPORT LoginPasswordView : public views::View,
void OnCapsLockChanged(bool enabled) override; void OnCapsLockChanged(bool enabled) override;
void OnKeyboardLayoutNameChanged(const std::string&) override {} void OnKeyboardLayoutNameChanged(const std::string&) override {}
void HandleLeftIconsVisibilities(bool handling_capslock);
// Submits the current password field text to mojo call and resets the text // Submits the current password field text to mojo call and resets the text
// field. // field.
void SubmitPassword(); void SubmitPassword();
...@@ -164,6 +164,7 @@ class ASH_EXPORT LoginPasswordView : public views::View, ...@@ -164,6 +164,7 @@ class ASH_EXPORT LoginPasswordView : public views::View,
class DisplayPasswordButton; class DisplayPasswordButton;
class LoginPasswordRow; class LoginPasswordRow;
class LoginTextfield; class LoginTextfield;
class AlternateIconsView;
friend class TestApi; friend class TestApi;
// Increases/decreases the contrast of the capslock icon. // Increases/decreases the contrast of the capslock icon.
...@@ -205,8 +206,12 @@ class ASH_EXPORT LoginPasswordView : public views::View, ...@@ -205,8 +206,12 @@ class ASH_EXPORT LoginPasswordView : public views::View,
LoginTextfield* textfield_ = nullptr; LoginTextfield* textfield_ = nullptr;
ArrowButtonView* submit_button_ = nullptr; ArrowButtonView* submit_button_ = nullptr;
DisplayPasswordButton* display_password_button_ = nullptr; DisplayPasswordButton* display_password_button_ = nullptr;
// Could show either the caps lock icon or the easy unlock icon.
AlternateIconsView* left_icon_ = nullptr;
views::ImageView* capslock_icon_ = nullptr; views::ImageView* capslock_icon_ = nullptr;
bool should_show_capslock_ = false;
EasyUnlockIcon* easy_unlock_icon_ = nullptr; EasyUnlockIcon* easy_unlock_icon_ = nullptr;
bool should_show_easy_unlock_ = false;
DISALLOW_COPY_AND_ASSIGN(LoginPasswordView); DISALLOW_COPY_AND_ASSIGN(LoginPasswordView);
}; };
......
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
#include "ash/shell.h" #include "ash/shell.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/timer/mock_timer.h"
#include "ui/base/ime/text_input_type.h" #include "ui/base/ime/text_input_type.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/events/event_constants.h" #include "ui/events/event_constants.h"
#include "ui/events/test/event_generator.h" #include "ui/events/test/event_generator.h"
#include "ui/views/controls/button/image_button.h" #include "ui/views/controls/button/image_button.h"
...@@ -22,6 +22,15 @@ namespace ash { ...@@ -22,6 +22,15 @@ namespace ash {
namespace { namespace {
constexpr base::TimeDelta kClearPasswordAfterDelay =
base::TimeDelta::FromSeconds(30);
constexpr base::TimeDelta kHidePasswordAfterDelay =
base::TimeDelta::FromSeconds(5);
constexpr base::TimeDelta kLeftIconDisplayTime =
base::TimeDelta::FromSeconds(4);
class LoginPasswordViewTest : public LoginTestBase { class LoginPasswordViewTest : public LoginTestBase {
protected: protected:
LoginPasswordViewTest() = default; LoginPasswordViewTest() = default;
...@@ -290,34 +299,88 @@ TEST_F(LoginPasswordViewTest, CtrlZDisabled) { ...@@ -290,34 +299,88 @@ TEST_F(LoginPasswordViewTest, CtrlZDisabled) {
EXPECT_TRUE(is_password_field_empty_); EXPECT_TRUE(is_password_field_empty_);
} }
// Ensures that the switch animation between easy unlock icon and caps lock
// icon works correctly.
TEST_F(LoginPasswordViewTest, SwitchBetweenEasyUnlockAndCapsLock) {
LoginPasswordView::TestApi test_api(view_);
// Ensure there is no left icon shown.
EXPECT_FALSE(test_api.easy_unlock_icon()->GetVisible());
EXPECT_FALSE(test_api.capslock_icon()->GetVisible());
// Show the easy unlock icon.
view_->SetEasyUnlockIcon(EasyUnlockIconId::SPINNER,
base::string16() /*accessibility_label*/);
// The easy unlock icon should be visible.
EXPECT_TRUE(test_api.easy_unlock_icon()->GetVisible());
EXPECT_FALSE(test_api.capslock_icon()->GetVisible());
// Animations must be enabled for fast-forwarding to work.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
view_->OnCapsLockChanged(true);
EXPECT_FALSE(test_api.easy_unlock_icon()->GetVisible());
EXPECT_TRUE(test_api.capslock_icon()->GetVisible());
// After a delay, the left icon should display the easy unlock icon again.
task_environment()->FastForwardBy(kLeftIconDisplayTime);
EXPECT_TRUE(test_api.easy_unlock_icon()->GetVisible());
EXPECT_FALSE(test_api.capslock_icon()->GetVisible());
// The Caps lock should be displayed again after a delay.
task_environment()->FastForwardBy(kLeftIconDisplayTime);
EXPECT_FALSE(test_api.easy_unlock_icon()->GetVisible());
EXPECT_TRUE(test_api.capslock_icon()->GetVisible());
// Disable Caps lock.
view_->OnCapsLockChanged(false);
// The easy unlock icon should be immediately visible.
EXPECT_TRUE(test_api.easy_unlock_icon()->GetVisible());
EXPECT_FALSE(test_api.capslock_icon()->GetVisible());
// Hide the easy unlock icon.
view_->SetEasyUnlockIcon(EasyUnlockIconId::NONE,
base::string16() /*accessibility_label*/);
// Nothing should be displayed.
EXPECT_FALSE(test_api.easy_unlock_icon()->GetVisible());
EXPECT_FALSE(test_api.capslock_icon()->GetVisible());
// This time, show the Caps lock icon first.
view_->OnCapsLockChanged(true);
EXPECT_FALSE(test_api.easy_unlock_icon()->GetVisible());
EXPECT_TRUE(test_api.capslock_icon()->GetVisible());
// Then trigger the easy unlock icon, it should be displayed immediately.
view_->SetEasyUnlockIcon(EasyUnlockIconId::SPINNER,
base::string16() /*accessibility_label*/);
EXPECT_TRUE(test_api.easy_unlock_icon()->GetVisible());
EXPECT_FALSE(test_api.capslock_icon()->GetVisible());
}
// Verifies that the password textfield clears after a delay when the display // Verifies that the password textfield clears after a delay when the display
// password button is shown. // password button is shown.
TEST_F(LoginPasswordViewTest, PasswordAutoClearsAndHides) { TEST_F(LoginPasswordViewTest, PasswordAutoClearsAndHides) {
LoginPasswordView::TestApi test_api(view_); LoginPasswordView::TestApi test_api(view_);
ui::test::EventGenerator* generator = GetEventGenerator(); ui::test::EventGenerator* generator = GetEventGenerator();
// Install mock timers into the password view. view_->SetDisplayPasswordButtonVisible(true);
auto clear_timer0 = std::make_unique<base::MockRetainingOneShotTimer>();
auto hide_timer0 = std::make_unique<base::MockRetainingOneShotTimer>();
base::MockRetainingOneShotTimer* clear_timer = clear_timer0.get();
base::MockRetainingOneShotTimer* hide_timer = hide_timer0.get();
test_api.SetTimers(std::move(clear_timer0), std::move(hide_timer0));
// Verify clearing timer works. // Verify clearing timer works.
generator->PressKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE); generator->PressKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
EXPECT_FALSE(is_password_field_empty_); EXPECT_FALSE(is_password_field_empty_);
clear_timer->Fire(); task_environment()->FastForwardBy(kClearPasswordAfterDelay);
EXPECT_TRUE(is_password_field_empty_); EXPECT_TRUE(is_password_field_empty_);
// Check a second time. // Check a second time.
generator->PressKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE); generator->PressKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
EXPECT_FALSE(is_password_field_empty_); EXPECT_FALSE(is_password_field_empty_);
clear_timer->Fire(); task_environment()->FastForwardBy(kClearPasswordAfterDelay);
EXPECT_TRUE(is_password_field_empty_); EXPECT_TRUE(is_password_field_empty_);
// Verify hiding timer works; set the password visible first then fire the // Verify hiding timer works; set the password visible first then wait for
// hiding timer and check it is hidden. // the hiding timer to trigger and check it is hidden.
generator->PressKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE); generator->PressKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
EXPECT_EQ(test_api.textfield()->GetTextInputType(), EXPECT_EQ(test_api.textfield()->GetTextInputType(),
ui::TEXT_INPUT_TYPE_PASSWORD); ui::TEXT_INPUT_TYPE_PASSWORD);
...@@ -325,11 +388,11 @@ TEST_F(LoginPasswordViewTest, PasswordAutoClearsAndHides) { ...@@ -325,11 +388,11 @@ TEST_F(LoginPasswordViewTest, PasswordAutoClearsAndHides) {
test_api.display_password_button()->GetBoundsInScreen().CenterPoint()); test_api.display_password_button()->GetBoundsInScreen().CenterPoint());
generator->ClickLeftButton(); generator->ClickLeftButton();
EXPECT_EQ(test_api.textfield()->GetTextInputType(), ui::TEXT_INPUT_TYPE_NULL); EXPECT_EQ(test_api.textfield()->GetTextInputType(), ui::TEXT_INPUT_TYPE_NULL);
hide_timer->Fire(); task_environment()->FastForwardBy(kHidePasswordAfterDelay);
EXPECT_EQ(test_api.textfield()->GetTextInputType(), EXPECT_EQ(test_api.textfield()->GetTextInputType(),
ui::TEXT_INPUT_TYPE_PASSWORD); ui::TEXT_INPUT_TYPE_PASSWORD);
// Hide an empty password already hidden and make sure a second fire works. // Hide an empty password already hidden and make sure a second trigger works.
hide_timer->Fire(); task_environment()->FastForwardBy(kHidePasswordAfterDelay);
EXPECT_EQ(test_api.textfield()->GetTextInputType(), EXPECT_EQ(test_api.textfield()->GetTextInputType(),
ui::TEXT_INPUT_TYPE_PASSWORD); ui::TEXT_INPUT_TYPE_PASSWORD);
} }
......
...@@ -20,7 +20,8 @@ ...@@ -20,7 +20,8 @@
namespace ash { namespace ash {
LoginTestBase::LoginTestBase() = default; LoginTestBase::LoginTestBase()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
LoginTestBase::~LoginTestBase() = default; LoginTestBase::~LoginTestBase() = default;
......
...@@ -34,6 +34,9 @@ class COMPOSITOR_EXPORT LayerAnimationObserver { ...@@ -34,6 +34,9 @@ class COMPOSITOR_EXPORT LayerAnimationObserver {
virtual void OnLayerAnimationEnded( virtual void OnLayerAnimationEnded(
LayerAnimationSequence* sequence) = 0; LayerAnimationSequence* sequence) = 0;
// Called when a |sequence| cycle ends. Not called if |sequence| is aborted.
virtual void OnLayerAnimationCycleEnded(LayerAnimationSequence* sequence) {}
// Called if |sequence| is aborted for any reason. Should never do anything // Called if |sequence| is aborted for any reason. Should never do anything
// that may cause another animation to be started. // that may cause another animation to be started.
virtual void OnLayerAnimationAborted( virtual void OnLayerAnimationAborted(
......
...@@ -66,6 +66,7 @@ void LayerAnimationSequence::Progress(base::TimeTicks now, ...@@ -66,6 +66,7 @@ void LayerAnimationSequence::Progress(base::TimeTicks now,
last_start_ = start_time_; last_start_ = start_time_;
size_t current_index = last_element_ % elements_.size(); size_t current_index = last_element_ % elements_.size();
bool just_completed_sequence = false;
base::TimeDelta element_duration; base::TimeDelta element_duration;
while (is_cyclic_ || last_element_ < elements_.size()) { while (is_cyclic_ || last_element_ < elements_.size()) {
elements_[current_index]->set_requested_start_time(last_start_); elements_[current_index]->set_requested_start_time(last_start_);
...@@ -80,6 +81,8 @@ void LayerAnimationSequence::Progress(base::TimeTicks now, ...@@ -80,6 +81,8 @@ void LayerAnimationSequence::Progress(base::TimeTicks now,
last_progressed_fraction_ = last_progressed_fraction_ =
elements_[current_index]->last_progressed_fraction(); elements_[current_index]->last_progressed_fraction();
current_index = last_element_ % elements_.size(); current_index = last_element_ % elements_.size();
DCHECK(last_element_ > 0);
just_completed_sequence = current_index == 0;
} }
if (is_cyclic_ || last_element_ < elements_.size()) { if (is_cyclic_ || last_element_ < elements_.size()) {
...@@ -101,11 +104,15 @@ void LayerAnimationSequence::Progress(base::TimeTicks now, ...@@ -101,11 +104,15 @@ void LayerAnimationSequence::Progress(base::TimeTicks now,
if (redraw_required) if (redraw_required)
delegate->ScheduleDrawForAnimation(); delegate->ScheduleDrawForAnimation();
if (!is_cyclic_ && last_element_ == elements_.size()) { if (just_completed_sequence) {
last_element_ = 0; if (!is_cyclic_) {
waiting_for_group_start_ = false; last_element_ = 0;
animation_group_id_ = 0; waiting_for_group_start_ = false;
NotifyEnded(); animation_group_id_ = 0;
NotifyEnded();
} else {
NotifyCycleEnded();
}
} }
} }
...@@ -158,6 +165,8 @@ void LayerAnimationSequence::ProgressToEnd(LayerAnimationDelegate* delegate) { ...@@ -158,6 +165,8 @@ void LayerAnimationSequence::ProgressToEnd(LayerAnimationDelegate* delegate) {
waiting_for_group_start_ = false; waiting_for_group_start_ = false;
animation_group_id_ = 0; animation_group_id_ = 0;
NotifyEnded(); NotifyEnded();
} else {
NotifyCycleEnded();
} }
} }
...@@ -280,6 +289,11 @@ void LayerAnimationSequence::NotifyEnded() { ...@@ -280,6 +289,11 @@ void LayerAnimationSequence::NotifyEnded() {
observer.OnLayerAnimationEnded(this); observer.OnLayerAnimationEnded(this);
} }
void LayerAnimationSequence::NotifyCycleEnded() {
for (auto& observer : observers_)
observer.OnLayerAnimationCycleEnded(this);
}
void LayerAnimationSequence::NotifyAborted() { void LayerAnimationSequence::NotifyAborted() {
for (auto& observer : observers_) for (auto& observer : observers_)
observer.OnLayerAnimationAborted(this); observer.OnLayerAnimationAborted(this);
......
...@@ -160,6 +160,9 @@ class COMPOSITOR_EXPORT LayerAnimationSequence ...@@ -160,6 +160,9 @@ class COMPOSITOR_EXPORT LayerAnimationSequence
// Notifies the observers that this sequence has ended. // Notifies the observers that this sequence has ended.
void NotifyEnded(); void NotifyEnded();
// Notifies the observers that this sequence cycle has ended.
void NotifyCycleEnded();
// Notifies the observers that this sequence has been aborted. // Notifies the observers that this sequence has been aborted.
void NotifyAborted(); void NotifyAborted();
......
This diff is collapsed.
...@@ -20,6 +20,8 @@ TestLayerAnimationObserver::TestLayerAnimationObserver() ...@@ -20,6 +20,8 @@ TestLayerAnimationObserver::TestLayerAnimationObserver()
last_aborted_sequence_epoch_(-1), last_aborted_sequence_epoch_(-1),
last_ended_sequence_(nullptr), last_ended_sequence_(nullptr),
last_ended_sequence_epoch_(-1), last_ended_sequence_epoch_(-1),
last_cycle_ended_sequence_(nullptr),
last_cycle_ended_sequence_epoch_(-1),
last_detached_sequence_(nullptr), last_detached_sequence_(nullptr),
last_detached_sequence_epoch_(-1), last_detached_sequence_epoch_(-1),
requires_notification_when_animator_destroyed_(false) {} requires_notification_when_animator_destroyed_(false) {}
...@@ -39,6 +41,8 @@ void TestLayerAnimationObserver::ResetLayerAnimationObserverations() { ...@@ -39,6 +41,8 @@ void TestLayerAnimationObserver::ResetLayerAnimationObserverations() {
last_aborted_sequence_epoch_ = -1; last_aborted_sequence_epoch_ = -1;
last_ended_sequence_ = nullptr; last_ended_sequence_ = nullptr;
last_ended_sequence_epoch_ = -1; last_ended_sequence_epoch_ = -1;
last_cycle_ended_sequence_ = nullptr;
last_cycle_ended_sequence_epoch_ = -1;
last_detached_sequence_ = nullptr; last_detached_sequence_ = nullptr;
last_detached_sequence_epoch_ = -1; last_detached_sequence_epoch_ = -1;
} }
...@@ -73,6 +77,12 @@ void TestLayerAnimationObserver::OnLayerAnimationEnded( ...@@ -73,6 +77,12 @@ void TestLayerAnimationObserver::OnLayerAnimationEnded(
last_ended_sequence_epoch_ = next_epoch_++; last_ended_sequence_epoch_ = next_epoch_++;
} }
void TestLayerAnimationObserver::OnLayerAnimationCycleEnded(
LayerAnimationSequence* sequence) {
last_cycle_ended_sequence_ = sequence;
last_cycle_ended_sequence_epoch_ = next_epoch_++;
}
void TestLayerAnimationObserver::OnDetachedFromSequence( void TestLayerAnimationObserver::OnDetachedFromSequence(
LayerAnimationSequence* sequence) { LayerAnimationSequence* sequence) {
last_detached_sequence_ = sequence; last_detached_sequence_ = sequence;
...@@ -87,7 +97,8 @@ TestLayerAnimationObserver::RequiresNotificationWhenAnimatorDestroyed() const { ...@@ -87,7 +97,8 @@ TestLayerAnimationObserver::RequiresNotificationWhenAnimatorDestroyed() const {
testing::AssertionResult TestLayerAnimationObserver::NoEventsObserved() { testing::AssertionResult TestLayerAnimationObserver::NoEventsObserved() {
if (!last_attached_sequence_ && !last_scheduled_sequence_ && if (!last_attached_sequence_ && !last_scheduled_sequence_ &&
!last_started_sequence_ && !last_aborted_sequence_ && !last_started_sequence_ && !last_aborted_sequence_ &&
!last_ended_sequence_ && !last_detached_sequence_) { !last_ended_sequence_ && !last_cycle_ended_sequence_ &&
!last_detached_sequence_) {
return testing::AssertionSuccess(); return testing::AssertionSuccess();
} else { } else {
testing::AssertionResult assertion_failure = testing::AssertionFailure(); testing::AssertionResult assertion_failure = testing::AssertionFailure();
...@@ -111,6 +122,10 @@ testing::AssertionResult TestLayerAnimationObserver::NoEventsObserved() { ...@@ -111,6 +122,10 @@ testing::AssertionResult TestLayerAnimationObserver::NoEventsObserved() {
if (last_ended_sequence_) { if (last_ended_sequence_) {
assertion_failure << "\n\tlast_ended_sequence_" << last_ended_sequence_; assertion_failure << "\n\tlast_ended_sequence_" << last_ended_sequence_;
} }
if (last_cycle_ended_sequence_) {
assertion_failure << "\n\tlast_cycle_ended_sequence_"
<< last_cycle_ended_sequence_;
}
if (last_detached_sequence_) { if (last_detached_sequence_) {
assertion_failure << "\n\tlast_detached_sequence_=" assertion_failure << "\n\tlast_detached_sequence_="
<< last_detached_sequence_; << last_detached_sequence_;
......
...@@ -28,6 +28,7 @@ class TestLayerAnimationObserver : public LayerAnimationObserver { ...@@ -28,6 +28,7 @@ class TestLayerAnimationObserver : public LayerAnimationObserver {
void OnLayerAnimationStarted(LayerAnimationSequence* sequence) override; void OnLayerAnimationStarted(LayerAnimationSequence* sequence) override;
void OnLayerAnimationAborted(LayerAnimationSequence* sequence) override; void OnLayerAnimationAborted(LayerAnimationSequence* sequence) override;
void OnLayerAnimationEnded(LayerAnimationSequence* sequence) override; void OnLayerAnimationEnded(LayerAnimationSequence* sequence) override;
void OnLayerAnimationCycleEnded(LayerAnimationSequence* sequence) override;
bool RequiresNotificationWhenAnimatorDestroyed() const override; bool RequiresNotificationWhenAnimatorDestroyed() const override;
const LayerAnimationSequence* last_attached_sequence() const { const LayerAnimationSequence* last_attached_sequence() const {
...@@ -68,6 +69,14 @@ class TestLayerAnimationObserver : public LayerAnimationObserver { ...@@ -68,6 +69,14 @@ class TestLayerAnimationObserver : public LayerAnimationObserver {
int last_ended_sequence_epoch() const { return last_ended_sequence_epoch_; } int last_ended_sequence_epoch() const { return last_ended_sequence_epoch_; }
const LayerAnimationSequence* last_cycle_ended_sequence() const {
return last_cycle_ended_sequence_;
}
int last_cycle_ended_sequence_epoch() const {
return last_cycle_ended_sequence_epoch_;
}
const LayerAnimationSequence* last_detached_sequence() const { const LayerAnimationSequence* last_detached_sequence() const {
return last_detached_sequence_; return last_detached_sequence_;
} }
...@@ -121,6 +130,9 @@ class TestLayerAnimationObserver : public LayerAnimationObserver { ...@@ -121,6 +130,9 @@ class TestLayerAnimationObserver : public LayerAnimationObserver {
const LayerAnimationSequence* last_ended_sequence_; const LayerAnimationSequence* last_ended_sequence_;
int last_ended_sequence_epoch_; int last_ended_sequence_epoch_;
const LayerAnimationSequence* last_cycle_ended_sequence_;
int last_cycle_ended_sequence_epoch_;
const LayerAnimationSequence* last_detached_sequence_; const LayerAnimationSequence* last_detached_sequence_;
int last_detached_sequence_epoch_; int last_detached_sequence_epoch_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment