blob: 51dac34cf28a4b4f10240cf805dfdca51d19a694 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/renderer_host/direct_manipulation_helper_win.h"
#include <objbase.h>
#include <cmath>
#include <utility>
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/win/win_util.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/win/window_event_target.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/compositor_animation_observer.h"
#include "ui/display/win/screen_win.h"
#include "ui/gfx/geometry/rect.h"
namespace content {
namespace {
constexpr gfx::Size kDefaultSize{1000, 1000};
} // namespace
// static
std::unique_ptr<DirectManipulationHelper>
DirectManipulationHelper::CreateInstance(HWND window) {
if (!::IsWindow(window)) {
return nullptr;
}
// IDirectManipulationUpdateManager is the first COM object created by the
// application to retrieve other objects in the Direct Manipulation API.
// It also serves to activate and deactivate Direct Manipulation functionality
// on a per-HWND basis.
ComPtr<IDirectManipulationManager> manager;
HRESULT hr = ::CoCreateInstance(CLSID_DirectManipulationManager, nullptr,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&manager));
if (!SUCCEEDED(hr)) {
return nullptr;
}
return CreateInstanceImpl(std::move(manager), window);
}
// static
std::unique_ptr<DirectManipulationHelper>
DirectManipulationHelper::CreateInstanceForTesting(
ComPtr<IDirectManipulationManager> manager) {
return CreateInstanceImpl(std::move(manager), /*window=*/nullptr);
}
// static
std::unique_ptr<DirectManipulationHelper>
DirectManipulationHelper::CreateInstanceImpl(
ComPtr<IDirectManipulationManager> manager,
HWND window) {
// Since we want to use fake viewport, we need UpdateManager to tell a fake
// fake render frame.
ComPtr<IDirectManipulationUpdateManager> update_manager;
HRESULT hr = manager->GetUpdateManager(IID_PPV_ARGS(&update_manager));
if (!SUCCEEDED(hr)) {
return nullptr;
}
ComPtr<IDirectManipulationViewport> viewport;
hr = manager->CreateViewport(nullptr, window, IID_PPV_ARGS(&viewport));
if (!SUCCEEDED(hr)) {
return nullptr;
}
absl::Cleanup clean_up_viewport_on_error = [&viewport] {
viewport->Abandon();
};
DIRECTMANIPULATION_CONFIGURATION configuration =
DIRECTMANIPULATION_CONFIGURATION_INTERACTION |
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X |
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y |
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA |
DIRECTMANIPULATION_CONFIGURATION_RAILS_X |
DIRECTMANIPULATION_CONFIGURATION_RAILS_Y |
DIRECTMANIPULATION_CONFIGURATION_SCALING;
hr = viewport->ActivateConfiguration(configuration);
if (!SUCCEEDED(hr)) {
return nullptr;
}
// Since we are using fake viewport and only want to use Direct Manipulation
// for touchpad, we need to use MANUALUPDATE option.
hr = viewport->SetViewportOptions(
DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE);
if (!SUCCEEDED(hr)) {
return nullptr;
}
// Set default rect for viewport before activate.
RECT rect = gfx::Rect(kDefaultSize).ToRECT();
hr = viewport->SetViewportRect(&rect);
if (!SUCCEEDED(hr)) {
return nullptr;
}
hr = manager->Activate(window);
if (!SUCCEEDED(hr)) {
return nullptr;
}
absl::Cleanup deactivate_manager_on_error = [&manager, &window] {
manager->Deactivate(window);
};
hr = viewport->Enable();
if (!SUCCEEDED(hr)) {
return nullptr;
}
std::move(deactivate_manager_on_error).Cancel();
std::move(clean_up_viewport_on_error).Cancel();
return base::WrapUnique(new DirectManipulationHelper(
std::move(manager), std::move(update_manager), std::move(viewport),
window));
}
DirectManipulationHelper::DirectManipulationHelper(
ComPtr<IDirectManipulationManager> manager,
ComPtr<IDirectManipulationUpdateManager> update_manager,
ComPtr<IDirectManipulationViewport> viewport,
HWND window)
: manager_(std::move(manager)),
update_manager_(std::move(update_manager)),
viewport_(std::move(viewport)),
window_(window),
size_in_pixels_(kDefaultSize) {}
DirectManipulationHelper::~DirectManipulationHelper() {
Destroy();
}
void DirectManipulationHelper::OnAnimationStep(base::TimeTicks timestamp) {
// Simulate 1 frame in update_manager_.
update_manager_->Update(nullptr);
}
void DirectManipulationHelper::OnCompositingShuttingDown(
ui::Compositor* notifying_compositor) {
DCHECK_EQ(notifying_compositor, compositor());
Destroy();
}
void DirectManipulationHelper::UpdateEventHandler(
base::WeakPtr<aura::WindowTreeHost> window_tree_host,
ui::WindowEventTarget* event_target) {
if (window_tree_host.get() != window_tree_host_.get() &&
has_animation_observer_) {
RemoveAnimationObserver();
}
if (event_handler_) {
event_handler_.Reset();
viewport_->Stop();
viewport_->RemoveEventHandler(view_port_handler_cookie_);
}
window_tree_host_ = window_tree_host;
event_target_ = event_target;
if (!event_target) {
// No need for an event handler without a target.
return;
}
event_handler_ = Microsoft::WRL::Make<DirectManipulationEventHandler>(
weak_factory_.GetWeakPtr());
event_handler_->SetViewportSizeInPixels(size_in_pixels_);
// We got Direct Manipulation transform from
// IDirectManipulationViewportEventHandler.
HRESULT hr = viewport_->AddEventHandler(window_, event_handler_.Get(),
&view_port_handler_cookie_);
if (!SUCCEEDED(hr)) {
event_handler_.Reset();
return;
}
update_manager_->Update(nullptr);
}
void DirectManipulationHelper::SetSizeInPixels(
const gfx::Size& size_in_pixels) {
if (size_in_pixels == size_in_pixels_) {
return;
}
size_in_pixels_ = size_in_pixels;
if (event_handler_) {
event_handler_->SetViewportSizeInPixels(size_in_pixels);
}
HRESULT hr = viewport_->Stop();
if (!SUCCEEDED(hr))
return;
RECT rect = gfx::Rect(size_in_pixels).ToRECT();
hr = viewport_->SetViewportRect(&rect);
}
void DirectManipulationHelper::OnPointerHitTest(WPARAM w_param) {
if (!event_handler_) {
return;
}
// Update the device scale factor.
event_handler_->SetDeviceScaleFactor(
display::win::GetScreenWin()->GetScaleFactorForHWND(window_));
// Only DM_POINTERHITTEST can be the first message of input sequence of
// touchpad input.
// TODO(chaopeng) Check if Windows API changes:
// For WM_POINTER, the pointer type will show the event from mouse.
// For WM_POINTERACTIVATE, the pointer id will be different with the following
// message.
UINT32 pointer_id = GET_POINTERID_WPARAM(w_param);
POINTER_INPUT_TYPE pointer_type;
if (::GetPointerType(pointer_id, &pointer_type) &&
pointer_type == PT_TOUCHPAD) {
viewport_->SetContact(pointer_id);
}
}
void DirectManipulationHelper::AddAnimationObserver() {
if (compositor()) {
compositor()->AddAnimationObserver(this);
}
has_animation_observer_ = true;
}
void DirectManipulationHelper::RemoveAnimationObserver() {
if (compositor()) {
compositor()->RemoveAnimationObserver(this);
}
has_animation_observer_ = false;
}
void DirectManipulationHelper::SetDeviceScaleFactorForTesting(float factor) {
DCHECK(event_handler_);
event_handler_->SetDeviceScaleFactor(factor);
}
void DirectManipulationHelper::Destroy() {
UpdateEventHandler(nullptr, nullptr);
viewport_->Abandon();
manager_->Deactivate(window_);
}
} // namespace content