chromium/ash/wm/overview/overview_grid_event_handler.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_grid_event_handler.h"

#include "ash/app_list/app_list_controller_impl.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "ash/wallpaper/views/wallpaper_view.h"
#include "ash/wallpaper/views/wallpaper_widget_controller.h"
#include "ash/wm/gestures/wm_fling_handler.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_utils.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "base/functional/bind.h"
#include "ui/display/screen.h"
#include "ui/events/event.h"
#include "ui/gfx/geometry/vector2d_f.h"
#include "ui/views/widget/widget.h"

namespace ash {

namespace {

// Do not bother moving the grid until a series of scrolls has reached this
// threshold.
constexpr float kScrollOffsetThresholdDp = 1.f;

WallpaperView* GetWallpaperViewForRoot(const aura::Window* root_window) {
  auto* wallpaper_widget_controller =
      RootWindowController::ForWindow(root_window)
          ->wallpaper_widget_controller();
  if (!wallpaper_widget_controller)
    return nullptr;
  return wallpaper_widget_controller->wallpaper_view();
}

}  // namespace

OverviewGridEventHandler::OverviewGridEventHandler(OverviewGrid* grid)
    : grid_(grid), overview_session_(grid_->overview_session()) {
  DCHECK(overview_session_);
  auto* wallpaper_view = GetWallpaperViewForRoot(grid_->root_window());
  if (wallpaper_view)
    wallpaper_view->AddPreTargetHandler(this);
}

OverviewGridEventHandler::~OverviewGridEventHandler() {
  OnFlingEnd();
  grid_->EndScroll();

  auto* wallpaper_view = GetWallpaperViewForRoot(grid_->root_window());
  if (wallpaper_view)
    wallpaper_view->RemovePreTargetHandler(this);
}

void OverviewGridEventHandler::OnMouseEvent(ui::MouseEvent* event) {
  // The following can only happen if a user is dragging a window with touch and
  // then they move the mouse to click on the wallpaper. This is an extreme edge
  // case, so just exit overview. Note that this is done here instead of on
  // release like usual, because pressing the mouse while dragging sends out a
  // ui::GESTURE_END_EVENT which may cause a bad state.
  if (event->type() == ui::EventType::kMousePressed &&
      !overview_session_->CanProcessEvent()) {
    OverviewController::Get()->EndOverview(
        OverviewEndAction::kClickingOutsideWindowsInOverview);
    event->StopPropagation();
    event->SetHandled();
    return;
  }

  if (event->type() == ui::EventType::kMouseReleased) {
    HandleClickOrTap(event);
  }
}

void OverviewGridEventHandler::OnGestureEvent(ui::GestureEvent* event) {
  if (!overview_session_->CanProcessEvent()) {
    event->StopPropagation();
    event->SetHandled();
    return;
  }

  // TODO(crbug.com/1341128): Enable context menu via long-press in library page
  // `SavedDeskLibraryView` will take over gesture event if it's active. When
  // it's `EventType::kGestureTap`, here it does not set event to handled, and
  // thus `HandleClickOrTap()` would be executed from
  // `SavedDeskLibraryView::OnLocatedEvent()`.
  if (grid_->IsShowingSavedDeskLibrary()) {
    return;
  }

  if (event->type() == ui::EventType::kGestureTap) {
    HandleClickOrTap(event);
    return;
  }

  // The following events are for scrolling the overview scroll layout, which is
  // tablet only.
  if (!display::Screen::GetScreen()->InTabletMode()) {
    return;
  }

  switch (event->type()) {
    case ui::EventType::kScrollFlingStart: {
      HandleFlingScroll(event);
      event->SetHandled();
      break;
    }
    case ui::EventType::kGestureScrollBegin: {
      scroll_offset_x_cumulative_ = 0.f;
      OnFlingEnd();
      grid_->StartScroll();
      event->SetHandled();
      break;
    }
    case ui::EventType::kGestureScrollUpdate: {
      // Only forward the scrolls to grid once they have exceeded the threshold.
      const float scroll_offset_x = event->details().scroll_x();
      scroll_offset_x_cumulative_ += scroll_offset_x;
      if (std::abs(scroll_offset_x_cumulative_) > kScrollOffsetThresholdDp) {
        grid_->UpdateScrollOffset(scroll_offset_x_cumulative_);
        scroll_offset_x_cumulative_ = 0.f;
      }
      event->SetHandled();
      break;
    }
    case ui::EventType::kGestureScrollEnd: {
      grid_->EndScroll();
      event->SetHandled();
      break;
    }
    default:
      break;
  }
}

void OverviewGridEventHandler::HandleClickOrTap(ui::Event* event) {
  CHECK_EQ(ui::EP_PRETARGET, event->phase());

  // If the user is renaming a desk or saved desk, rather than closing overview
  // the focused name view should lose focus.
  if (grid_->IsDeskNameBeingModified() ||
      grid_->IsSavedDeskNameBeingModified()) {
    grid_->CommitNameChanges();
    event->StopPropagation();
    return;
  }

  if (display::Screen::GetScreen()->InTabletMode()) {
    aura::Window* window = static_cast<views::View*>(event->target())
                               ->GetWidget()
                               ->GetNativeWindow();

    // In tablet mode, clicking on tapping on the wallpaper background will
    // head back to home launcher screen if not in split view (in which case
    // the event should be ignored).
    if (!SplitViewController::Get(window)->InSplitViewMode()) {
      int64_t display_id =
          display::Screen::GetScreen()->GetDisplayNearestWindow(window).id();
      Shell::Get()->app_list_controller()->GoHome(display_id);
    }
  } else {
    OverviewController::Get()->EndOverview(
        OverviewEndAction::kClickingOutsideWindowsInOverview);
  }
  event->StopPropagation();
}

void OverviewGridEventHandler::HandleFlingScroll(ui::GestureEvent* event) {
  const gfx::Vector2dF initial_fling_velocity(event->details().velocity_x(),
                                              event->details().velocity_y());
  fling_handler_ = std::make_unique<WmFlingHandler>(
      initial_fling_velocity, grid_->root_window(),
      base::BindRepeating(&OverviewGridEventHandler::OnFlingStep,
                          base::Unretained(this)),
      base::BindRepeating(&OverviewGridEventHandler::OnFlingEnd,
                          base::Unretained(this)));
}

bool OverviewGridEventHandler::OnFlingStep(float offset) {
  // Updates `grid_` based on `offset`.
  DCHECK(fling_handler_);
  return grid_->UpdateScrollOffset(offset);
}

void OverviewGridEventHandler::OnFlingEnd() {
  if (!fling_handler_)
    return;

  fling_handler_.reset();
  grid_->EndScroll();
}

}  // namespace ash