expand_arrow_view.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. // Copyright 2017 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/app_list/views/expand_arrow_view.h"
  5. #include <algorithm>
  6. #include <memory>
  7. #include <utility>
  8. #include "ash/app_list/views/app_list_view.h"
  9. #include "ash/app_list/views/apps_container_view.h"
  10. #include "ash/app_list/views/contents_view.h"
  11. #include "ash/public/cpp/app_list/app_list_color_provider.h"
  12. #include "ash/public/cpp/app_list/app_list_config.h"
  13. #include "ash/public/cpp/app_list/vector_icons/vector_icons.h"
  14. #include "ash/strings/grit/ash_strings.h"
  15. #include "base/bind.h"
  16. #include "base/metrics/histogram_macros.h"
  17. #include "base/numerics/safe_conversions.h"
  18. #include "ui/base/l10n/l10n_util.h"
  19. #include "ui/compositor/scoped_animation_duration_scale_mode.h"
  20. #include "ui/gfx/animation/slide_animation.h"
  21. #include "ui/gfx/canvas.h"
  22. #include "ui/gfx/color_palette.h"
  23. #include "ui/gfx/paint_vector_icon.h"
  24. #include "ui/views/animation/flood_fill_ink_drop_ripple.h"
  25. #include "ui/views/animation/ink_drop.h"
  26. #include "ui/views/animation/ink_drop_impl.h"
  27. #include "ui/views/controls/highlight_path_generator.h"
  28. namespace ash {
  29. namespace {
  30. // The dimensions of this view.
  31. constexpr int kTileHeight = 72;
  32. constexpr int kTileWidth = 60;
  33. // The arrow dimension of expand arrow.
  34. constexpr int kArrowDimension = 12;
  35. constexpr int kInkDropRadius = 18;
  36. constexpr int kCircleRadius = 18;
  37. // The y position of circle center in closed, peeking and fullscreen state.
  38. constexpr int kCircleCenterClosedY = 18;
  39. constexpr int kCircleCenterPeekingY = 42;
  40. constexpr int kCircleCenterFullscreenY = 8;
  41. // The arrow's y position in peeking and fullscreen state.
  42. constexpr int kArrowClosedY = 12;
  43. constexpr int kArrowPeekingY = 36;
  44. constexpr int kArrowFullscreenY = 2;
  45. // The stroke width of the arrow.
  46. constexpr int kExpandArrowStrokeWidth = 2;
  47. // The three points of arrow in peeking and fullscreen state.
  48. constexpr size_t kPointCount = 3;
  49. constexpr gfx::PointF kPeekingPoints[] = {
  50. gfx::PointF(0, kArrowDimension / 4 * 3),
  51. gfx::PointF(kArrowDimension / 2, kArrowDimension / 4),
  52. gfx::PointF(kArrowDimension, kArrowDimension / 4 * 3)};
  53. constexpr gfx::PointF kFullscreenPoints[] = {
  54. gfx::PointF(0, kArrowDimension / 2),
  55. gfx::PointF(kArrowDimension / 2, kArrowDimension / 2),
  56. gfx::PointF(kArrowDimension, kArrowDimension / 2)};
  57. // Arrow vertical transiton animation related constants.
  58. constexpr int kTotalArrowYOffset = 24;
  59. constexpr int kPulseMinRadius = 2;
  60. constexpr int kPulseMaxRadius = 30;
  61. constexpr float kPulseMinOpacity = 0.f;
  62. constexpr float kPulseMaxOpacity = 0.3f;
  63. constexpr int kAnimationInitialWaitTimeInSec = 3;
  64. constexpr int kAnimationIntervalInSec = 10;
  65. constexpr auto kCycleDuration = base::Milliseconds(1000);
  66. constexpr auto kCycleInterval = base::Milliseconds(500);
  67. constexpr int kFocusRingWidth = 2;
  68. // THe bounds for the tap target of the expand arrow button.
  69. constexpr int kTapTargetWidth = 156;
  70. constexpr int kTapTargetHeight = 72;
  71. float GetCircleCenterYForAppListProgress(float progress) {
  72. if (progress <= 1) {
  73. return gfx::Tween::FloatValueBetween(progress, kCircleCenterClosedY,
  74. kCircleCenterPeekingY);
  75. }
  76. return gfx::Tween::FloatValueBetween(std::min(1.0f, progress - 1),
  77. kCircleCenterPeekingY,
  78. kCircleCenterFullscreenY);
  79. }
  80. float GetArrowYForAppListProgress(float progress) {
  81. if (progress <= 1) {
  82. return gfx::Tween::FloatValueBetween(progress, kArrowClosedY,
  83. kArrowPeekingY);
  84. }
  85. return gfx::Tween::FloatValueBetween(std::min(1.0f, progress - 1),
  86. kArrowPeekingY, kArrowFullscreenY);
  87. }
  88. // Returns the location of the circle, relative to the view's local bounds.
  89. gfx::Rect GetCircleBounds() {
  90. const gfx::Point circle_center(kTileWidth / 2, kCircleCenterPeekingY);
  91. const gfx::Rect circle_bounds(
  92. circle_center - gfx::Vector2d(kCircleRadius, kCircleRadius),
  93. gfx::Size(2 * kCircleRadius, 2 * kCircleRadius));
  94. return circle_bounds;
  95. }
  96. class ExpandArrowHighlightPathGenerator : public views::HighlightPathGenerator {
  97. public:
  98. ExpandArrowHighlightPathGenerator() = default;
  99. ExpandArrowHighlightPathGenerator(const ExpandArrowHighlightPathGenerator&) =
  100. delete;
  101. ExpandArrowHighlightPathGenerator& operator=(
  102. const ExpandArrowHighlightPathGenerator&) = delete;
  103. // views::HighlightPathGenerator:
  104. absl::optional<gfx::RRectF> GetRoundRect(const gfx::RectF& rect) override {
  105. return gfx::RRectF(gfx::RectF(GetCircleBounds()), kInkDropRadius);
  106. }
  107. };
  108. } // namespace
  109. ExpandArrowView::ExpandArrowView(ContentsView* contents_view,
  110. AppListView* app_list_view)
  111. : views::Button(base::BindRepeating(&ExpandArrowView::OnButtonPressed,
  112. base::Unretained(this))),
  113. contents_view_(contents_view),
  114. app_list_view_(app_list_view) {
  115. SetFocusBehavior(FocusBehavior::ALWAYS);
  116. SetPaintToLayer();
  117. layer()->SetFillsBoundsOpaquely(false);
  118. // ExpandArrowView draws its own focus, removing FocusRing prevents double
  119. // focus.
  120. // TODO(pbos): Replace ::OnPaint focus painting with FocusRing +
  121. // HighlightPathGenerator usage.
  122. SetInstallFocusRingOnFocus(false);
  123. views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON);
  124. views::HighlightPathGenerator::Install(
  125. this, std::make_unique<ExpandArrowHighlightPathGenerator>());
  126. views::InkDrop::UseInkDropWithoutAutoHighlight(views::InkDrop::Get(this),
  127. /*highlight_on_hover=*/false);
  128. views::InkDrop::Get(this)->SetCreateRippleCallback(base::BindRepeating(
  129. [](Button* host) -> std::unique_ptr<views::InkDropRipple> {
  130. const AppListColorProvider* color_provider =
  131. AppListColorProvider::Get();
  132. return std::make_unique<views::FloodFillInkDropRipple>(
  133. host->size(), host->GetLocalBounds().InsetsFrom(GetCircleBounds()),
  134. views::InkDrop::Get(host)->GetInkDropCenterBasedOnLastEvent(),
  135. color_provider->GetInkDropBaseColor(),
  136. color_provider->GetInkDropOpacity());
  137. },
  138. this));
  139. SetAccessibleName(l10n_util::GetStringUTF16(IDS_APP_LIST_EXPAND_BUTTON));
  140. animation_ = std::make_unique<gfx::SlideAnimation>(this);
  141. animation_->SetTweenType(gfx::Tween::LINEAR);
  142. animation_->SetSlideDuration(kCycleDuration * 2 + kCycleInterval);
  143. SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
  144. }
  145. ExpandArrowView::~ExpandArrowView() = default;
  146. void ExpandArrowView::PaintButtonContents(gfx::Canvas* canvas) {
  147. gfx::PointF circle_center(kTileWidth / 2, kCircleCenterPeekingY);
  148. gfx::PointF arrow_origin((kTileWidth - kArrowDimension) / 2, kArrowPeekingY);
  149. gfx::PointF arrow_points[kPointCount];
  150. for (size_t i = 0; i < kPointCount; ++i)
  151. arrow_points[i] = kPeekingPoints[i];
  152. SkColor circle_color =
  153. AppListColorProvider::Get()->GetExpandArrowIconBackgroundColor();
  154. const float progress = app_list_view_->GetAppListTransitionProgress(
  155. AppListView::kProgressFlagNone);
  156. circle_center.set_y(GetCircleCenterYForAppListProgress(progress));
  157. arrow_origin.set_y(GetArrowYForAppListProgress(progress));
  158. // If transition progress is between peeking and fullscreen state, change
  159. // the shape of the arrow and the opacity of the circle in addition to
  160. // changing the circle and arrow position.
  161. if (progress > 1) {
  162. const float peeking_to_full_progress = progress - 1;
  163. for (size_t i = 0; i < kPointCount; ++i) {
  164. arrow_points[i].set_y(gfx::Tween::FloatValueBetween(
  165. peeking_to_full_progress, kPeekingPoints[i].y(),
  166. kFullscreenPoints[i].y()));
  167. }
  168. circle_color = gfx::Tween::ColorValueBetween(
  169. peeking_to_full_progress, circle_color, SK_ColorTRANSPARENT);
  170. }
  171. if (animation_->is_animating() && progress <= 1) {
  172. // If app list is peeking state or below peeking state, the arrow should
  173. // keep runing transition animation.
  174. arrow_origin.Offset(0, arrow_y_offset_);
  175. }
  176. // Draw a circle.
  177. cc::PaintFlags circle_flags;
  178. circle_flags.setAntiAlias(true);
  179. circle_flags.setColor(circle_color);
  180. circle_flags.setStyle(cc::PaintFlags::kFill_Style);
  181. canvas->DrawCircle(circle_center, kCircleRadius, circle_flags);
  182. if (HasFocus()) {
  183. cc::PaintFlags focus_ring_flags;
  184. focus_ring_flags.setAntiAlias(true);
  185. focus_ring_flags.setColor(AppListColorProvider::Get()->GetFocusRingColor());
  186. focus_ring_flags.setStyle(cc::PaintFlags::Style::kStroke_Style);
  187. focus_ring_flags.setStrokeWidth(kFocusRingWidth);
  188. // Creates a focus ring with 1px wider radius to create a border.
  189. canvas->DrawCircle(circle_center, kCircleRadius + 1, focus_ring_flags);
  190. }
  191. if (animation_->is_animating() && progress <= 1) {
  192. // Draw a pulse that expands around the circle.
  193. cc::PaintFlags pulse_flags;
  194. pulse_flags.setStyle(cc::PaintFlags::kStroke_Style);
  195. pulse_flags.setColor(
  196. SkColorSetA(AppListColorProvider::Get()->GetExpandArrowIconBaseColor(),
  197. static_cast<U8CPU>(255 * pulse_opacity_)));
  198. pulse_flags.setAntiAlias(true);
  199. canvas->DrawCircle(circle_center, pulse_radius_, pulse_flags);
  200. }
  201. // Add a clip path so that arrow will only be shown within the circular
  202. // highlight area.
  203. SkPath arrow_mask_path;
  204. arrow_mask_path.addCircle(circle_center.x(), circle_center.y(),
  205. kCircleRadius);
  206. canvas->ClipPath(arrow_mask_path, true);
  207. // Draw an arrow. (It becomes a horizontal line in fullscreen state.)
  208. for (auto& point : arrow_points)
  209. point.Offset(arrow_origin.x(), arrow_origin.y());
  210. cc::PaintFlags arrow_flags;
  211. arrow_flags.setAntiAlias(true);
  212. arrow_flags.setColor(
  213. AppListColorProvider::Get()->GetExpandArrowIconBaseColor());
  214. arrow_flags.setStrokeWidth(kExpandArrowStrokeWidth);
  215. arrow_flags.setStrokeCap(cc::PaintFlags::Cap::kRound_Cap);
  216. arrow_flags.setStrokeJoin(cc::PaintFlags::Join::kRound_Join);
  217. arrow_flags.setStyle(cc::PaintFlags::kStroke_Style);
  218. SkPath arrow_path;
  219. arrow_path.moveTo(arrow_points[0].x(), arrow_points[0].y());
  220. for (size_t i = 1; i < kPointCount; ++i)
  221. arrow_path.lineTo(arrow_points[i].x(), arrow_points[i].y());
  222. canvas->DrawPath(arrow_path, arrow_flags);
  223. }
  224. gfx::Size ExpandArrowView::CalculatePreferredSize() const {
  225. return gfx::Size(kTileWidth, kTileHeight);
  226. }
  227. bool ExpandArrowView::OnKeyPressed(const ui::KeyEvent& event) {
  228. if (event.key_code() != ui::VKEY_RETURN)
  229. return false;
  230. TransitToFullscreenAllAppsState();
  231. return true;
  232. }
  233. void ExpandArrowView::OnFocus() {
  234. SchedulePaint();
  235. Button::OnFocus();
  236. }
  237. void ExpandArrowView::OnBlur() {
  238. SchedulePaint();
  239. Button::OnBlur();
  240. }
  241. const char* ExpandArrowView::GetClassName() const {
  242. return "ExpandArrowView";
  243. }
  244. void ExpandArrowView::AnimationProgressed(const gfx::Animation* animation) {
  245. // There are two cycles in one animation.
  246. constexpr auto kAnimationDuration = kCycleDuration * 2 + kCycleInterval;
  247. constexpr auto kFirstCycleEndTime = kCycleDuration;
  248. constexpr auto kIntervalEndTime = kCycleDuration + kCycleInterval;
  249. constexpr auto kSecondCycleEndTime = kCycleDuration * 2 + kCycleInterval;
  250. base::TimeDelta time = animation->GetCurrentValue() * kAnimationDuration;
  251. if (time > kFirstCycleEndTime && time <= kIntervalEndTime) {
  252. // There's no animation in the interval between cycles.
  253. return;
  254. }
  255. if (time > kIntervalEndTime && time <= kSecondCycleEndTime) {
  256. // Convert to time in one single cycle.
  257. time -= kIntervalEndTime;
  258. }
  259. // Update pulse opacity.
  260. constexpr auto kPulseOpacityShowBeginTime = base::Milliseconds(100);
  261. constexpr auto kPulseOpacityShowEndTime = base::Milliseconds(200);
  262. constexpr auto kPulseOpacityHideBeginTime = base::Milliseconds(800);
  263. constexpr auto kPulseOpacityHideEndTime = base::Milliseconds(1000);
  264. if (time > kPulseOpacityShowBeginTime && time <= kPulseOpacityShowEndTime) {
  265. pulse_opacity_ =
  266. kPulseMinOpacity +
  267. (kPulseMaxOpacity - kPulseMinOpacity) *
  268. (time - kPulseOpacityShowBeginTime) /
  269. (kPulseOpacityShowEndTime - kPulseOpacityShowBeginTime);
  270. } else if (time > kPulseOpacityHideBeginTime &&
  271. time <= kPulseOpacityHideEndTime) {
  272. pulse_opacity_ =
  273. kPulseMaxOpacity -
  274. (kPulseMaxOpacity - kPulseMinOpacity) *
  275. (time - kPulseOpacityHideBeginTime) /
  276. (kPulseOpacityHideEndTime - kPulseOpacityHideBeginTime);
  277. }
  278. // Update pulse radius.
  279. pulse_radius_ =
  280. base::ClampRound((kPulseMaxRadius - kPulseMinRadius) *
  281. gfx::Tween::CalculateValue(gfx::Tween::EASE_IN_OUT,
  282. time / kCycleDuration));
  283. // Update y position offset of the arrow.
  284. constexpr auto kArrowMoveOutBeginTime = base::Milliseconds(100);
  285. constexpr auto kArrowMoveOutEndTime = base::Milliseconds(500);
  286. constexpr auto kArrowMoveInBeginTime = base::Milliseconds(500);
  287. constexpr auto kArrowMoveInEndTime = base::Milliseconds(900);
  288. if (time > kArrowMoveOutBeginTime && time <= kArrowMoveOutEndTime) {
  289. const double progress = (time - kArrowMoveOutBeginTime) /
  290. (kArrowMoveOutEndTime - kArrowMoveOutBeginTime);
  291. arrow_y_offset_ = base::ClampRound(
  292. -kTotalArrowYOffset *
  293. gfx::Tween::CalculateValue(gfx::Tween::EASE_IN, progress));
  294. } else if (time > kArrowMoveInBeginTime && time <= kArrowMoveInEndTime) {
  295. const double progress = (time - kArrowMoveInBeginTime) /
  296. (kArrowMoveInEndTime - kArrowMoveInBeginTime);
  297. arrow_y_offset_ = base::ClampRound(
  298. kTotalArrowYOffset *
  299. (1 - gfx::Tween::CalculateValue(gfx::Tween::EASE_OUT, progress)));
  300. }
  301. // Apply updates.
  302. SchedulePaint();
  303. }
  304. void ExpandArrowView::AnimationEnded(const gfx::Animation* /*animation*/) {
  305. ResetHintingAnimation();
  306. // Only reschedule hinting animation if app list is not fullscreen. Once the
  307. // user has made the app_list fullscreen, a hint to do so is no longer needed
  308. if (!app_list_view_->is_fullscreen())
  309. ScheduleHintingAnimation(false);
  310. }
  311. void ExpandArrowView::OnButtonPressed() {
  312. button_pressed_ = true;
  313. ResetHintingAnimation();
  314. TransitToFullscreenAllAppsState();
  315. views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
  316. views::InkDropState::ACTION_TRIGGERED);
  317. }
  318. void ExpandArrowView::TransitToFullscreenAllAppsState() {
  319. UMA_HISTOGRAM_ENUMERATION("Apps.AppListPageOpened", AppListState::kStateApps,
  320. AppListState::kStateLast);
  321. UMA_HISTOGRAM_ENUMERATION(kAppListPeekingToFullscreenHistogram, kExpandArrow,
  322. kMaxPeekingToFullscreen);
  323. contents_view_->SetActiveState(AppListState::kStateApps);
  324. app_list_view_->SetState(AppListViewState::kFullscreenAllApps);
  325. }
  326. void ExpandArrowView::MaybeEnableHintingAnimation(bool enabled) {
  327. button_pressed_ = false;
  328. ResetHintingAnimation();
  329. // When side shelf or tablet mode is enabled, the peeking launcher won't be
  330. // shown, so the hint animation is unnecessary. Also, do not run the animation
  331. // during test since we are not testing the animation and it might cause msan
  332. // crash when spoken feedback is enabled (See https://crbug.com/926038).
  333. if (enabled && !app_list_view_->is_side_shelf() &&
  334. !app_list_view_->is_tablet_mode() &&
  335. !ui::ScopedAnimationDurationScaleMode::is_zero()) {
  336. ScheduleHintingAnimation(true);
  337. } else {
  338. hinting_animation_timer_.Stop();
  339. }
  340. }
  341. float ExpandArrowView::CalculateOffsetFromCurrentAppListProgress(
  342. double progress) const {
  343. const float current_progress = app_list_view_->GetAppListTransitionProgress(
  344. AppListView::kProgressFlagNone);
  345. return GetCircleCenterYForAppListProgress(progress) -
  346. GetCircleCenterYForAppListProgress(current_progress);
  347. }
  348. void ExpandArrowView::ScheduleHintingAnimation(bool is_first_time) {
  349. int delay_in_sec = kAnimationIntervalInSec;
  350. if (is_first_time)
  351. delay_in_sec = kAnimationInitialWaitTimeInSec;
  352. hinting_animation_timer_.Start(FROM_HERE, base::Seconds(delay_in_sec), this,
  353. &ExpandArrowView::StartHintingAnimation);
  354. }
  355. void ExpandArrowView::StartHintingAnimation() {
  356. if (!button_pressed_)
  357. animation_->Show();
  358. }
  359. void ExpandArrowView::ResetHintingAnimation() {
  360. pulse_opacity_ = kPulseMinOpacity;
  361. pulse_radius_ = kPulseMinRadius;
  362. animation_->Reset();
  363. Layout();
  364. }
  365. bool ExpandArrowView::DoesIntersectRect(const views::View* target,
  366. const gfx::Rect& rect) const {
  367. DCHECK_EQ(target, this);
  368. gfx::Rect button_bounds = GetLocalBounds();
  369. // Increase clickable area for the button from
  370. // (kTileWidth x height) to
  371. // (kTapTargetWidth x kTapTargetHeight).
  372. const int horizontal_padding = (kTapTargetWidth - button_bounds.width()) / 2;
  373. const int vertical_padding = (kTapTargetHeight - button_bounds.height()) / 2;
  374. button_bounds.Inset(gfx::Insets::VH(-vertical_padding, -horizontal_padding));
  375. return button_bounds.Intersects(rect);
  376. }
  377. } // namespace ash