chromium/ash/wm/overview/overview_item_view.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/wm/overview/overview_item_view.h"

#include <memory>

#include "ash/strings/grit/ash_strings.h"
#include "ash/style/close_button.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_item.h"
#include "ash/wm/overview/overview_types.h"
#include "ash/wm/snap_group/snap_group.h"
#include "ash/wm/snap_group/snap_group_controller.h"
#include "ash/wm/window_mini_view_header_view.h"
#include "ash/wm/window_preview_view.h"
#include "ash/wm/window_util.h"
#include "ash/wm/wm_constants.h"
#include "base/containers/contains.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/animation_builder.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/controls/label.h"
#include "ui/views/widget/widget.h"

namespace ash {

namespace {

// Duration of the show/hide animation of the header.
constexpr base::TimeDelta kHeaderFadeDuration = base::Milliseconds(167);

// Delay before the show animation of the header.
constexpr base::TimeDelta kHeaderFadeInDelay = base::Milliseconds(83);

// Duration of the slow show animation of the close button.
constexpr base::TimeDelta kCloseButtonSlowFadeInDuration =
    base::Milliseconds(300);

// Delay before the slow show animation of the close button.
constexpr base::TimeDelta kCloseButtonSlowFadeInDelay = base::Milliseconds(750);

// Animates |layer| from 0 -> 1 opacity if |visible| and 1 -> 0 opacity
// otherwise. The tween type differs for |visible| and if |visible| is true
// there is a slight delay before the animation begins. Does not animate if
// opacity matches |visible|.
void AnimateLayerOpacity(ui::Layer* layer, bool visible) {
  float target_opacity = visible ? 1.f : 0.f;
  if (layer->GetTargetOpacity() == target_opacity)
    return;

  views::AnimationBuilder()
      .SetPreemptionStrategy(ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS)
      .Once()
      .SetDuration(base::TimeDelta())
      .SetOpacity(layer, 1.f - target_opacity)
      .At(visible ? kHeaderFadeInDelay : base::TimeDelta())
      .SetDuration(kHeaderFadeDuration)
      .SetOpacity(layer, target_opacity,
                  visible ? gfx::Tween::LINEAR_OUT_SLOW_IN
                          : gfx::Tween::FAST_OUT_LINEAR_IN);
}

}  // namespace

OverviewItemView::OverviewItemView(
    OverviewItem* overview_item,
    EventHandlerDelegate* event_handler_delegate,
    views::Button::PressedCallback close_callback,
    aura::Window* window,
    bool show_preview)
    : WindowMiniView(window, /*use_custom_focus_predicate=*/false),
      overview_item_(overview_item),
      event_handler_delegate_(event_handler_delegate),
      close_button_(header_view()->icon_label_view()->AddChildView(
          std::make_unique<CloseButton>(std::move(close_callback),
                                        CloseButton::Type::kMediumFloating))) {
  CHECK(overview_item_);
  // Focusable so we can add accelerators to this view.
  SetFocusBehavior(views::View::FocusBehavior::ALWAYS);

  views::InkDrop::Get(close_button_)
      ->SetMode(views::InkDropHost::InkDropMode::ON_NO_GESTURE_HANDLER);
  close_button_->GetViewAccessibility().SetName(
      l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
  close_button_->SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);

  header_view()->UpdateIconView(window);

  AddAccelerator(ui::Accelerator(ui::VKEY_W, ui::EF_CONTROL_DOWN));
  AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, 0));
  AddAccelerator(ui::Accelerator(ui::VKEY_SPACE, 0));

  // Call this last as it triggers layout, which relies on some of the other
  // elements existing.
  SetShowPreview(show_preview);

  // TODO: This doesn't allow |this| to be navigated by ChromeVox, find a way
  // to allow |this| as well as the title and close button.
  GetViewAccessibility().SetRole(ax::mojom::Role::kGenericContainer);
  UpdateAccessibleDescription();
}

OverviewItemView::~OverviewItemView() = default;

void OverviewItemView::SetCloseButtonVisible(bool visible) {
  if (!close_button_->layer()) {
    close_button_->SetPaintToLayer();
    close_button_->layer()->SetFillsBoundsOpaquely(false);
  }

  AnimateLayerOpacity(close_button_->layer(), visible);
  close_button_->SetEnabled(visible);
}

void OverviewItemView::HideCloseInstantlyAndThenShowItSlowly() {
  CHECK(close_button_);

  if (!close_button_->layer()) {
    close_button_->SetPaintToLayer();
    close_button_->layer()->SetFillsBoundsOpaquely(false);
  }

  ui::Layer* layer = close_button_->layer();

  views::AnimationBuilder()
      .SetPreemptionStrategy(ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS)
      .Once()
      .SetDuration(base::TimeDelta())
      .SetOpacity(layer, 0.f)
      .At(kCloseButtonSlowFadeInDelay)
      .SetDuration(kCloseButtonSlowFadeInDuration)
      .SetOpacity(layer, 1.f, gfx::Tween::FAST_OUT_SLOW_IN);

  close_button_->SetEnabled(true);
}

void OverviewItemView::OnOverviewItemWindowRestoring() {
  // Explicitly reset `overview_item_` and `event_handler_delegate_` to avoid
  // dangling pointer since the corresponding `item_widget_` may outlive its
  // corresponding `overview_item_` see `FadeOutWidgetFromOverview()` in
  // `overview_utils.cc` for example.
  overview_item_ = nullptr;
  event_handler_delegate_ = nullptr;
  close_button_->ResetListener();
}

void OverviewItemView::RefreshPreviewView() {
  if (!preview_view())
    return;

  preview_view()->RecreatePreviews();
  DeprecatedLayoutImmediately();
}

void OverviewItemView::AcceptSelection(OverviewSession* overview_session) {
  CHECK(overview_session);
  overview_session->SelectWindow(overview_item_);
}

gfx::Size OverviewItemView::GetPreviewViewSize() const {
  // The preview should expand to fit the bounds allocated for the content,
  // except if it is letterboxed or pillarboxed.
  const gfx::SizeF preview_pref_size(preview_view()->GetPreferredSize());
  const float aspect_ratio =
      preview_pref_size.width() / preview_pref_size.height();
  gfx::SizeF target_size(GetContentAreaBounds().size());
  OverviewItemFillMode fill_mode =
      overview_item_ ? overview_item_->GetOverviewItemFillMode()
                     : OverviewItemFillMode::kNormal;
  switch (fill_mode) {
    case OverviewItemFillMode::kNormal:
      break;
    case OverviewItemFillMode::kLetterBoxed:
      target_size.set_height(target_size.width() / aspect_ratio);
      break;
    case OverviewItemFillMode::kPillarBoxed:
      target_size.set_width(target_size.height() * aspect_ratio);
      break;
  }

  return gfx::ToRoundedSize(target_size);
}

void OverviewItemView::RefreshItemVisuals() {
  // `overview_item_` may get reset in `OnOverviewItemWindowRestoring()` since
  // the corresponding `item_widget_` may outlive its corresponding
  // `overview_item_`, we want to avoid `overview_item_` from being accessed
  // again.
  if (!overview_item_) {
    return;
  }

  // Set the rounded corners to accommodate for the customized rounded corners
  // needed for the overview group item.
  if (SnapGroupController* snap_group_controller = SnapGroupController::Get()) {
    const aura::Window* window = overview_item_->GetWindow();
    if (snap_group_controller->GetSnapGroupForGivenWindow(window)) {
      SetRoundedCornersRadius(window_util::GetMiniWindowRoundedCorners(
          window, /*include_header_rounding=*/true));

      // `SetRoundedCornersRadius()` will trigger rounded corners update for
      // header view, preview view and focus ring automatically. Early return to
      // avoid duplicate updates.
      return;
    }
  }

  ResetRoundedCorners();
}

bool OverviewItemView::OnMousePressed(const ui::MouseEvent& event) {
  if (!event_handler_delegate_) {
    return views::View::OnMousePressed(event);
  }

  event_handler_delegate_->HandleMouseEvent(event, overview_item_);
  return true;
}

bool OverviewItemView::OnMouseDragged(const ui::MouseEvent& event) {
  if (!event_handler_delegate_) {
    return views::View::OnMouseDragged(event);
  }

  event_handler_delegate_->HandleMouseEvent(event, overview_item_);
  return true;
}

void OverviewItemView::OnMouseReleased(const ui::MouseEvent& event) {
  if (!event_handler_delegate_) {
    views::View::OnMouseReleased(event);
    return;
  }

  event_handler_delegate_->HandleMouseEvent(event, overview_item_);
}

void OverviewItemView::OnGestureEvent(ui::GestureEvent* event) {
  if (!event_handler_delegate_) {
    return;
  }

  event_handler_delegate_->HandleGestureEvent(event, overview_item_);
  event->SetHandled();
}

bool OverviewItemView::CanAcceptEvent(const ui::Event& event) {
  bool accept_events = true;
  // Do not process or accept press down events that are on the border.
  static ui::EventType press_types[] = {ui::EventType::kGestureTapDown,
                                        ui::EventType::kMousePressed};
  if (event.IsLocatedEvent() && base::Contains(press_types, event.type())) {
    const gfx::Rect content_bounds = GetContentsBounds();
    if (!content_bounds.Contains(event.AsLocatedEvent()->location()))
      accept_events = false;
  }

  return accept_events && views::View::CanAcceptEvent(event);
}

bool OverviewItemView::AcceleratorPressed(const ui::Accelerator& accelerator) {
  if (accelerator.IsCtrlDown() && accelerator.key_code() == ui::VKEY_W) {
    if (overview_item_) {
      overview_item_->OnFocusedViewClosed();
    }
    return true;
  }

  if (accelerator.key_code() == ui::VKEY_RETURN ||
      accelerator.key_code() == ui::VKEY_SPACE) {
    if (overview_item_) {
      overview_item_->OnFocusedViewActivated();
    }
    return true;
  }
  return WindowMiniView::AcceleratorPressed(accelerator);
}

bool OverviewItemView::CanHandleAccelerators() const {
  return HasFocus() && WindowMiniView::CanHandleAccelerators();
}

void OverviewItemView::OnWindowDestroying(aura::Window* window) {
  WindowMiniView::OnWindowDestroying(window);
  UpdateAccessibleDescription();
}

void OverviewItemView::UpdateAccessibleDescription() {
  const bool is_group_item = [&]() {
    auto* snap_group_controller = SnapGroupController::Get();
    return snap_group_controller &&
           snap_group_controller->GetSnapGroupForGivenWindow(source_window());
  }();

  GetViewAccessibility().SetDescription(l10n_util::GetStringUTF8(
      is_group_item ? IDS_ASH_SNAP_GROUP_WINDOW_CYCLE_DESCRIPTION
                    : IDS_ASH_OVERVIEW_CLOSABLE_HIGHLIGHT_ITEM_A11Y_EXTRA_TIP));
}

BEGIN_METADATA(OverviewItemView)
END_METADATA

}  // namespace ash