Commit 5fc9f5ca authored by Carlos IL's avatar Carlos IL Committed by Commit Bot

Add gradient mask to omnibox elide animation

Adds a rectangle that gradients from omnibox background color to
transparent on the left side of the omnibox, and an inverted one to
the right.
The rectangles are used as gradient masks when an elide animation is
running, to smooth out the color transition at the end.
The mask is fixed size, except if they would obscure the not-elided part
of the URL, in which case they shrink.

Screencast of change (public link): https://docs.google.com/presentation/d/1AGXJNkt8_oQyPLSDOfAGduscL0LRCCUcZq5XtJo2eC0/edit?usp=sharing

Bug: 1106467
Change-Id: Ic5029bbc850285ebe4e5e76a5a3d810c965f7e44
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2321883
Commit-Queue: Carlos IL <carlosil@chromium.org>
Reviewed-by: default avatarTommy Li <tommycli@chromium.org>
Cr-Commit-Position: refs/heads/master@{#794235}
parent 0a332812
...@@ -140,6 +140,22 @@ bool IsClipboardDataMarkedAsConfidential() { ...@@ -140,6 +140,22 @@ bool IsClipboardDataMarkedAsConfidential() {
->IsMarkedByOriginatorAsConfidential(); ->IsMarkedByOriginatorAsConfidential();
} }
// Draws a rectangle of dimensions and position |rect| in |canvas|, colored
// with a gradient from |start_color| to |end_color|.
void DrawGradientRect(const gfx::Rect& rect,
SkColor start_color,
SkColor end_color,
gfx::Canvas* canvas) {
SkColor colors[2] = {start_color, end_color};
SkPoint points[2];
points[0].iset(rect.origin().x(), rect.origin().y());
points[1].iset(rect.right(), rect.y());
cc::PaintFlags flags;
flags.setShader(cc::PaintShader::MakeLinearGradient(points, colors, nullptr,
2, SkTileMode::kClamp));
canvas->DrawRect(rect, flags);
}
} // namespace } // namespace
OmniboxViewViews::ElideAnimation::ElideAnimation(OmniboxViewViews* view, OmniboxViewViews::ElideAnimation::ElideAnimation(OmniboxViewViews* view,
...@@ -157,13 +173,30 @@ OmniboxViewViews::ElideAnimation::~ElideAnimation() = default; ...@@ -157,13 +173,30 @@ OmniboxViewViews::ElideAnimation::~ElideAnimation() = default;
void OmniboxViewViews::ElideAnimation::Start( void OmniboxViewViews::ElideAnimation::Start(
const gfx::Range& elide_to_bounds, const gfx::Range& elide_to_bounds,
uint32_t delay_ms, uint32_t delay_ms,
const std::vector<gfx::Range>& ranges_to_color, const std::vector<gfx::Range>& ranges_surrounding_simplified_domain,
SkColor starting_color, SkColor starting_color,
SkColor ending_color) { SkColor ending_color) {
ranges_to_color_ = ranges_to_color; DCHECK(ranges_surrounding_simplified_domain.size() == 1 ||
ranges_surrounding_simplified_domain.size() == 2);
ranges_surrounding_simplified_domain_ = ranges_surrounding_simplified_domain;
starting_color_ = starting_color; starting_color_ = starting_color;
ending_color_ = ending_color; ending_color_ = ending_color;
// simplified_domain_bounds_ will be set to a rectangle surrounding the part
// of the URL that is never elided, on its original position before any
// animation runs. If ranges_surrounding_simplified_domain_ only contains one
// range it means we are not eliding on the right side, so we use the right
// side of elide_to_bounds as the range as it will always be the right limit
// of the simplified section.
gfx::Range simplified_domain_range(
ranges_surrounding_simplified_domain_[0].end(),
ranges_surrounding_simplified_domain_.size() == 2
? ranges_surrounding_simplified_domain_[1].start()
: elide_to_bounds.end());
for (auto rect : render_text_->GetSubstringBounds(simplified_domain_range)) {
simplified_domain_bounds_.Union(rect - render_text_->GetLineOffset(0));
}
// After computing |elide_to_rect_| below, |elide_to_bounds| aren't actually // After computing |elide_to_rect_| below, |elide_to_bounds| aren't actually
// need anymore for the animation. However, the bounds provide a convenient // need anymore for the animation. However, the bounds provide a convenient
// way for the animation consumer to check if an animation is currently in // way for the animation consumer to check if an animation is currently in
...@@ -248,15 +281,35 @@ void OmniboxViewViews::ElideAnimation::AnimationProgressed( ...@@ -248,15 +281,35 @@ void OmniboxViewViews::ElideAnimation::AnimationProgressed(
gfx::Rect shifted_bounds(old_bounds.x(), old_bounds.y(), bounds.width(), gfx::Rect shifted_bounds(old_bounds.x(), old_bounds.y(), bounds.width(),
old_bounds.height()); old_bounds.height());
render_text_->SetDisplayRect(shifted_bounds); render_text_->SetDisplayRect(shifted_bounds);
current_offset_ = gfx::Tween::IntValueBetween(animation->GetCurrentValue(),
starting_display_offset_,
ending_display_offset_);
render_text_->SetDisplayOffset(current_offset_);
render_text_->SetDisplayOffset(gfx::Tween::IntValueBetween( for (const auto& range : ranges_surrounding_simplified_domain_) {
animation->GetCurrentValue(), starting_display_offset_,
ending_display_offset_));
for (const auto& range : ranges_to_color_) {
view_->ApplyColor(GetCurrentColor(), range); view_->ApplyColor(GetCurrentColor(), range);
} }
// The gradient mask should be a fixed width, except if that width would
// cause it to mask the unelided section. In that case we set it to the
// maximum width possible that won't cover the unelided section.
int unelided_left_bound = simplified_domain_bounds_.x() + current_offset_;
int unelided_right_bound =
unelided_left_bound + simplified_domain_bounds_.width();
int left_gradient_width = kSmoothingGradientMaxWidth < unelided_left_bound
? kSmoothingGradientMaxWidth
: unelided_left_bound;
int right_gradient_width =
shifted_bounds.right() - kSmoothingGradientMaxWidth > unelided_right_bound
? kSmoothingGradientMaxWidth
: shifted_bounds.right() - unelided_right_bound;
view_->elide_animation_smoothing_rect_left_ = gfx::Rect(
old_bounds.x(), old_bounds.y(), left_gradient_width, old_bounds.height());
view_->elide_animation_smoothing_rect_right_ =
gfx::Rect(shifted_bounds.right() - right_gradient_width, old_bounds.y(),
right_gradient_width, old_bounds.height());
view_->SchedulePaint(); view_->SchedulePaint();
} }
...@@ -615,6 +668,22 @@ void OmniboxViewViews::OnPaint(gfx::Canvas* canvas) { ...@@ -615,6 +668,22 @@ void OmniboxViewViews::OnPaint(gfx::Canvas* canvas) {
SCOPED_UMA_HISTOGRAM_TIMER("Omnibox.PaintTime"); SCOPED_UMA_HISTOGRAM_TIMER("Omnibox.PaintTime");
Textfield::OnPaint(canvas); Textfield::OnPaint(canvas);
} }
if ((hover_elide_or_unelide_animation_ &&
hover_elide_or_unelide_animation_->IsAnimating()) ||
(elide_after_web_contents_interaction_animation_ &&
elide_after_web_contents_interaction_animation_->IsAnimating())) {
SkColor bg_color = GetBackgroundColor();
// We can't use the SK_ColorTRANSPARENT constant here because for purposes
// of the gradient the R,G,B values of the transparent color do matter, and
// need to be identical to the background color (SK_ColorTRANSPARENT is a
// transparent black, and results in the gradient looking gray).
SkColor bg_transparent = SkColorSetARGB(
0, SkColorGetR(bg_color), SkColorGetG(bg_color), SkColorGetB(bg_color));
DrawGradientRect(elide_animation_smoothing_rect_left_, bg_color,
bg_transparent, canvas);
DrawGradientRect(elide_animation_smoothing_rect_right_, bg_transparent,
bg_color, canvas);
}
} }
void OmniboxViewViews::ExecuteCommand(int command_id, int event_flags) { void OmniboxViewViews::ExecuteCommand(int command_id, int event_flags) {
...@@ -2280,10 +2349,10 @@ gfx::Range OmniboxViewViews::GetSimplifiedDomainBounds( ...@@ -2280,10 +2349,10 @@ gfx::Range OmniboxViewViews::GetSimplifiedDomainBounds(
base::string16 text = GetText(); base::string16 text = GetText();
url::Component host = GetHostComponentAfterTrivialSubdomain(); url::Component host = GetHostComponentAfterTrivialSubdomain();
ranges_surrounding_simplified_domain->emplace_back(host.end(), text.size());
if (!OmniboxFieldTrial::ShouldElideToRegistrableDomain()) { if (!OmniboxFieldTrial::ShouldElideToRegistrableDomain()) {
ranges_surrounding_simplified_domain->emplace_back(0, host.begin); ranges_surrounding_simplified_domain->emplace_back(0, host.begin);
ranges_surrounding_simplified_domain->emplace_back(host.end(), text.size());
return gfx::Range(host.begin, host.end()); return gfx::Range(host.begin, host.end());
} }
...@@ -2295,6 +2364,7 @@ gfx::Range OmniboxViewViews::GetSimplifiedDomainBounds( ...@@ -2295,6 +2364,7 @@ gfx::Range OmniboxViewViews::GetSimplifiedDomainBounds(
if (simplified_domain.empty()) { if (simplified_domain.empty()) {
ranges_surrounding_simplified_domain->emplace_back(0, host.begin); ranges_surrounding_simplified_domain->emplace_back(0, host.begin);
ranges_surrounding_simplified_domain->emplace_back(host.end(), text.size());
return gfx::Range(host.begin, host.end()); return gfx::Range(host.begin, host.end());
} }
...@@ -2302,6 +2372,7 @@ gfx::Range OmniboxViewViews::GetSimplifiedDomainBounds( ...@@ -2302,6 +2372,7 @@ gfx::Range OmniboxViewViews::GetSimplifiedDomainBounds(
text.find(base::ASCIIToUTF16(simplified_domain)); text.find(base::ASCIIToUTF16(simplified_domain));
DCHECK_NE(simplified_domain_pos, std::string::npos); DCHECK_NE(simplified_domain_pos, std::string::npos);
ranges_surrounding_simplified_domain->emplace_back(0, simplified_domain_pos); ranges_surrounding_simplified_domain->emplace_back(0, simplified_domain_pos);
ranges_surrounding_simplified_domain->emplace_back(host.end(), text.size());
return gfx::Range(simplified_domain_pos, host.end()); return gfx::Range(simplified_domain_pos, host.end());
} }
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include "components/search_engines/template_url_service.h" #include "components/search_engines/template_url_service.h"
#include "components/search_engines/template_url_service_observer.h" #include "components/search_engines/template_url_service_observer.h"
#include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_observer.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/window_open_disposition.h" #include "ui/base/window_open_disposition.h"
#include "ui/compositor/compositor.h" #include "ui/compositor/compositor.h"
#include "ui/compositor/compositor_observer.h" #include "ui/compositor/compositor_observer.h"
...@@ -71,6 +72,9 @@ class OmniboxViewViews : public OmniboxView, ...@@ -71,6 +72,9 @@ class OmniboxViewViews : public OmniboxView,
static const int kMaxSendTabToSelfSubMenuCommandId = static const int kMaxSendTabToSelfSubMenuCommandId =
send_tab_to_self::SendTabToSelfSubMenuModel::kMaxCommandId; send_tab_to_self::SendTabToSelfSubMenuModel::kMaxCommandId;
// Max width of the gradient mask used to smooth ElideAnimation edges.
static const int kSmoothingGradientMaxWidth = 15;
OmniboxViewViews(OmniboxEditController* controller, OmniboxViewViews(OmniboxEditController* controller,
std::unique_ptr<OmniboxClient> client, std::unique_ptr<OmniboxClient> client,
bool popup_window_mode, bool popup_window_mode,
...@@ -198,6 +202,7 @@ class OmniboxViewViews : public OmniboxView, ...@@ -198,6 +202,7 @@ class OmniboxViewViews : public OmniboxView,
SameDocNavigations); SameDocNavigations);
FRIEND_TEST_ALL_PREFIXES(OmniboxViewViewsHideOnInteractionTest, FRIEND_TEST_ALL_PREFIXES(OmniboxViewViewsHideOnInteractionTest,
SameDocNavigationDuringAnimation); SameDocNavigationDuringAnimation);
FRIEND_TEST_ALL_PREFIXES(OmniboxViewViewsHideOnInteractionTest, GradientMask);
FRIEND_TEST_ALL_PREFIXES(OmniboxViewViewsHideOnInteractionTest, FRIEND_TEST_ALL_PREFIXES(OmniboxViewViewsHideOnInteractionTest,
UserInteractionDuringAnimation); UserInteractionDuringAnimation);
FRIEND_TEST_ALL_PREFIXES(OmniboxViewViewsHideOnInteractionTest, FRIEND_TEST_ALL_PREFIXES(OmniboxViewViewsHideOnInteractionTest,
...@@ -242,13 +247,18 @@ class OmniboxViewViews : public OmniboxView, ...@@ -242,13 +247,18 @@ class OmniboxViewViews : public OmniboxView,
~ElideAnimation() override; ~ElideAnimation() override;
// Begin the elision animation targeting |elide_to_bounds|, after a delay of // Begin the elision animation targeting |elide_to_bounds|, after a delay of
// |delay_ms|. As the animation runs, each range in |ranges_to_color| will // |delay_ms|. |ranges_surrounding_simplified_domain| should contain 1 or 2
// be faded from |starting_color| to |ending_color|. // ranges surrounding the simplified domain part, they should be in order
void Start(const gfx::Range& elide_to_bounds, // (i.e. the range on the left should be the first element). If only one
uint32_t delay_ms, // element is set, it will be assumed we are only eliding from the left
const std::vector<gfx::Range>& ranges_to_color, // side. Those ranges will be faded from |starting_color| to
SkColor starting_color, // |ending_color|.
SkColor ending_color); void Start(
const gfx::Range& elide_to_bounds,
uint32_t delay_ms,
const std::vector<gfx::Range>& ranges_surrounding_simplified_domain,
SkColor starting_color,
SkColor ending_color);
void Stop(); void Stop();
...@@ -260,13 +270,16 @@ class OmniboxViewViews : public OmniboxView, ...@@ -260,13 +270,16 @@ class OmniboxViewViews : public OmniboxView,
const gfx::Range& GetElideToBounds() const; const gfx::Range& GetElideToBounds() const;
// Returns the current color applied to each of the ranges in // Returns the current color applied to each of the ranges in
// |ranges_to_color| passed in to Start(), if the animation is running or // |ranges_surrounding_simplified_domain| passed in to Start(), if the
// has completed running. Returns gfx::kPlaceholderColor if the animation // animation is running or has completed running.
// has not starting running yet. // Returns gfx::kPlaceholderColor if the animation has not starting
// running yet.
SkColor GetCurrentColor() const; SkColor GetCurrentColor() const;
gfx::MultiAnimation* GetAnimationForTesting(); gfx::MultiAnimation* GetAnimationForTesting();
int GetCurrentOffsetForTesting() { return current_offset_; }
// views::AnimationDelegateViews: // views::AnimationDelegateViews:
void AnimationProgressed(const gfx::Animation* animation) override; void AnimationProgressed(const gfx::Animation* animation) override;
void AnimationEnded(const gfx::Animation* animation) override; void AnimationEnded(const gfx::Animation* animation) override;
...@@ -284,13 +297,18 @@ class OmniboxViewViews : public OmniboxView, ...@@ -284,13 +297,18 @@ class OmniboxViewViews : public OmniboxView,
gfx::Rect elide_to_rect_; gfx::Rect elide_to_rect_;
// The starting display rect from which we are eliding or uneliding. // The starting display rect from which we are eliding or uneliding.
gfx::Rect elide_from_rect_; gfx::Rect elide_from_rect_;
// The display rect surrounding the simplified domain.
gfx::Rect simplified_domain_bounds_;
// The starting and ending display offsets for |render_text_|. // The starting and ending display offsets for |render_text_|.
int starting_display_offset_ = 0; int starting_display_offset_ = 0;
int ending_display_offset_ = 0; int ending_display_offset_ = 0;
// As the animation runs, each range in |ranges_to_color_| fades from // The current offset, exposed for testing.
// |starting_color_| to |ending_color_|. int current_offset_;
std::vector<gfx::Range> ranges_to_color_;
// Holds the ranges surrounding the simplified domain part. As the animation
// runs, each range fades from |starting_color_| to |ending_color_|.
std::vector<gfx::Range> ranges_surrounding_simplified_domain_;
SkColor starting_color_; SkColor starting_color_;
SkColor ending_color_; SkColor ending_color_;
...@@ -525,6 +543,11 @@ class OmniboxViewViews : public OmniboxView, ...@@ -525,6 +543,11 @@ class OmniboxViewViews : public OmniboxView,
std::unique_ptr<ElideAnimation> std::unique_ptr<ElideAnimation>
elide_after_web_contents_interaction_animation_; elide_after_web_contents_interaction_animation_;
// If set, rectangles will be drawn as gradient masks over the omnibox text.
// Used to smooth color transition when an ElideAnimation is animating.
gfx::Rect elide_animation_smoothing_rect_left_;
gfx::Rect elide_animation_smoothing_rect_right_;
// Selection persisted across temporary text changes, like popup suggestions. // Selection persisted across temporary text changes, like popup suggestions.
std::vector<gfx::Range> saved_temporary_selection_; std::vector<gfx::Range> saved_temporary_selection_;
......
...@@ -2424,6 +2424,75 @@ TEST_P(OmniboxViewViewsHideOnInteractionTest, ...@@ -2424,6 +2424,75 @@ TEST_P(OmniboxViewViewsHideOnInteractionTest,
render_text, kSimplifiedDomainDisplayUrl, path_bounds)); render_text, kSimplifiedDomainDisplayUrl, path_bounds));
} }
// Tests that gradient mask is set correctly.
TEST_P(OmniboxViewViewsHideOnInteractionTest, GradientMask) {
SetUpSimplifiedDomainTest();
gfx::RenderText* render_text = omnibox_view()->GetRenderText();
content::MockNavigationHandle navigation;
navigation.set_is_same_document(false);
omnibox_view()->DidFinishNavigation(&navigation);
ASSERT_NO_FATAL_FAILURE(ExpectUnelidedFromSimplifiedDomain(
render_text, gfx::Range(kSimplifiedDomainDisplayUrlScheme.size(),
kSimplifiedDomainDisplayUrl.size())));
// Simulate a user interaction to begin animating to the simplified domain.
omnibox_view()->DidGetUserInteraction(blink::WebKeyboardEvent());
OmniboxViewViews::ElideAnimation* elide_animation =
omnibox_view()->GetElideAfterInteractionAnimationForTesting();
ASSERT_TRUE(elide_animation);
EXPECT_TRUE(elide_animation->IsAnimating());
// Advance the clock by 1ms until the full size gradient has been added.
gfx::AnimationContainerElement* elide_as_element =
elide_animation->GetAnimationForTesting();
elide_as_element->SetStartTime(base::TimeTicks());
uint32_t step = 1;
int max_gradient_width = OmniboxViewViews::kSmoothingGradientMaxWidth;
while (omnibox_view()->elide_animation_smoothing_rect_right_.width() <
max_gradient_width) {
elide_as_element->Step(base::TimeTicks() +
base::TimeDelta::FromMilliseconds(++step));
}
// If we are eliding from the left, the other side gradient should also be
// full size at this point, otherwise it should be 0.
if (GetParam()) {
EXPECT_EQ(omnibox_view()->elide_animation_smoothing_rect_left_.width(),
max_gradient_width);
} else {
EXPECT_EQ(omnibox_view()->elide_animation_smoothing_rect_left_.width(), 0);
}
// Get a bounding box for the unelided section of the URL.
std::vector<gfx::Range> ranges_surrounding_simplified_domain;
gfx::Range simplified_range = omnibox_view()->GetSimplifiedDomainBounds(
&ranges_surrounding_simplified_domain);
gfx::Rect simplified_rect;
for (auto rect : render_text->GetSubstringBounds(simplified_range)) {
simplified_rect.Union(rect - render_text->GetLineOffset(0));
}
// Advance the animation until both gradients start shrinking.
while (omnibox_view()->elide_animation_smoothing_rect_left_.width() ==
max_gradient_width ||
omnibox_view()->elide_animation_smoothing_rect_right_.width() ==
max_gradient_width) {
elide_as_element->Step(base::TimeTicks() +
base::TimeDelta::FromMilliseconds(++step));
}
int offset = elide_animation->GetCurrentOffsetForTesting();
gfx::Rect display_rect = render_text->display_rect();
// Check the expected size and positions for both gradients.
EXPECT_EQ(omnibox_view()->elide_animation_smoothing_rect_left_.width(),
simplified_rect.x() + offset);
EXPECT_EQ(omnibox_view()->elide_animation_smoothing_rect_left_.x(),
display_rect.x());
EXPECT_EQ(omnibox_view()->elide_animation_smoothing_rect_right_.width(),
display_rect.right() - (simplified_rect.right() + offset));
EXPECT_EQ(omnibox_view()->elide_animation_smoothing_rect_right_.x(),
simplified_rect.right() + offset);
}
// Tests that in the hide-on-interaction field trial, a second user interaction // Tests that in the hide-on-interaction field trial, a second user interaction
// does not interfere with an animation that is currently running. This is // does not interfere with an animation that is currently running. This is
// similar to SameDocNavigationDuringAnimation except that this test checks that // similar to SameDocNavigationDuringAnimation except that this test checks that
......
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