blob: f4edb195a6af127e9eae421e6140d2cb688be795 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/windows/game_pad_cursor_winuwp.h"
#include "game_pad_cursor_winuwp.h"
namespace flutter {
static constexpr int32_t kDefaultPointerDeviceId = 0;
GamepadCursorWinUWP::GamepadCursorWinUWP(
WindowBindingHandlerDelegate* view,
DisplayHelperWinUWP* displayhelper,
winrt::Windows::UI::Core::CoreWindow const& window,
winrt::Windows::UI::Composition::Compositor const& compositor,
winrt::Windows::UI::Composition::VisualCollection const& rootcollection) {
window_ = window;
root_collection_ = rootcollection;
binding_handler_delegate_ = view;
display_helper_ = displayhelper;
compositor_ = compositor;
cursor_visual_ = CreateCursorVisual();
ConfigureGamepad();
}
void GamepadCursorWinUWP::StartGamepadTimer() {
winrt::Windows::UI::Core::CoreDispatcher dispatcher = window_.Dispatcher();
dispatcher.RunAsync(
winrt::Windows::UI::Core::CoreDispatcherPriority::Normal,
[this, dispatcher]() {
auto queue =
winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
if (cursor_move_timer_ == nullptr) {
cursor_move_timer_ = queue.CreateTimer();
cursor_move_timer_.Interval(
std::chrono::milliseconds(kNormalPollingFrequency));
cursor_move_timer_.Tick(
[=](winrt::Windows::System::DispatcherQueueTimer timer,
winrt::Windows::Foundation::IInspectable const&) {
game_pad_->Process();
});
cursor_move_timer_.Start();
}
});
}
void GamepadCursorWinUWP::StopGamepadTimer() {
winrt::Windows::UI::Core::CoreDispatcher dispatcher = window_.Dispatcher();
dispatcher.RunAsync(winrt::Windows::UI::Core::CoreDispatcherPriority::Normal,
[this, dispatcher]() {
if (cursor_move_timer_ != nullptr) {
cursor_move_timer_.Stop();
cursor_move_timer_ = nullptr;
}
});
}
void GamepadCursorWinUWP::ConfigureGamepad() {
GamepadDualAxisCallback leftStick = [=](double x, double y) {
OnGamepadLeftStickMoved(x, y);
};
GamepadDualAxisCallback rightStick = [=](double x, double y) {
OnGamepadRightStickMoved(x, y);
};
GamepadButtonCallback pressedcallback =
[=](winrt::Windows::Gaming::Input::GamepadButtons buttons) {
OnGamepadButtonPressed(buttons);
};
GamepadButtonCallback releasedcallback =
[=](winrt::Windows::Gaming::Input::GamepadButtons buttons) {
OnGamepadButtonReleased(buttons);
};
GamepadAddedRemovedCallback changed = [=]() {
OnGamepadControllersChanged();
};
game_pad_ = std::make_unique<GamepadWinUWP>(leftStick, rightStick, nullptr,
nullptr, pressedcallback,
releasedcallback, changed);
game_pad_->Initialize();
SetCursorTimeout();
}
void GamepadCursorWinUWP::SetCursorTimeout() {
if (cursor_visual_ == nullptr) {
return;
}
auto queue = winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
// Lazily create timer.
if (emulated_cursor_hide_timer_ == nullptr) {
emulated_cursor_hide_timer_ = queue.CreateTimer();
emulated_cursor_hide_timer_.Interval(std::chrono::seconds(kInactivePeriod));
emulated_cursor_hide_timer_.Tick(
[=](winrt::Windows::System::DispatcherQueueTimer timer,
winrt::Windows::Foundation::IInspectable const&) {
// Timer fires when user has been inactive. At this point stop the
// timer. It will be restarted next time controller interaction
// happens triggering SetCursorTimeout
timer.Stop();
// If the timer fires, the user hasn't move the cursor for more than
// the interval hence hide the cursor
cursor_visual_.IsVisible(false);
// Reduce mouse move polling frequency while user not interacting with
// controller
SetMouseMovePollingFrequency(kReducedPollingFrequency);
});
}
// Ensure the cursor is visible and restart the timer as the user is
// interacting.
cursor_visual_.IsVisible(true);
emulated_cursor_hide_timer_.Start();
SetMouseMovePollingFrequency(kNormalPollingFrequency);
}
void GamepadCursorWinUWP::SetMouseMovePollingFrequency(int ms) {
if (cursor_move_timer_ != nullptr) {
cursor_move_timer_.Interval(std::chrono::milliseconds(ms));
}
}
winrt::Windows::Foundation::Numerics::float3
GamepadCursorWinUWP::GetScaledInput(
const winrt::Windows::Foundation::Numerics::float3 input) {
if (!display_helper_->IsRunningOnLargeScreenDevice()) {
const float inverse_dpi_scale = display_helper_->GetDpiScale();
return {cursor_visual_.Offset().x * inverse_dpi_scale,
cursor_visual_.Offset().y * inverse_dpi_scale, 1.0f};
} else {
return std::move(input);
}
}
void GamepadCursorWinUWP::OnGamepadLeftStickMoved(double x, double y) {
SetCursorTimeout();
float new_x =
cursor_visual_.Offset().x + (kCursorScale * static_cast<float>(x));
float new_y =
cursor_visual_.Offset().y + (kCursorScale * -static_cast<float>(y));
WindowBoundsWinUWP logical_bounds = display_helper_->GetLogicalBounds();
if (new_x > 0 && new_y > 0 && new_x < logical_bounds.width &&
new_y < logical_bounds.height) {
cursor_visual_.Offset({new_x, new_y, 0});
winrt::Windows::Foundation::Numerics::float3 scaled =
GetScaledInput(cursor_visual_.Offset());
// TODO(dnfield): Support for gamepad as a distinct device type?
// https://github.com/flutter/flutter/issues/80472
binding_handler_delegate_->OnPointerMove(scaled.x, scaled.y,
kFlutterPointerDeviceKindMouse,
kDefaultPointerDeviceId);
}
}
void GamepadCursorWinUWP::OnGamepadRightStickMoved(double x, double y) {
winrt::Windows::Foundation::Numerics::float3 scaled =
GetScaledInput(cursor_visual_.Offset());
binding_handler_delegate_->OnScroll(
scaled.x, scaled.y, x * kControllerScrollMultiplier,
y * kControllerScrollMultiplier, 1, kFlutterPointerDeviceKindMouse,
kDefaultPointerDeviceId);
}
void GamepadCursorWinUWP::OnGamepadButtonPressed(
winrt::Windows::Gaming::Input::GamepadButtons buttons) {
if ((buttons & winrt::Windows::Gaming::Input::GamepadButtons::A) ==
winrt::Windows::Gaming::Input::GamepadButtons::A) {
winrt::Windows::Foundation::Numerics::float3 scaled =
GetScaledInput(cursor_visual_.Offset());
// TODO(clarkezone) complete keyboard handling including
// system key (back), unicode handling, shortcut support,
// handling defered delivery, remove the need for action value.
// https://github.com/flutter/flutter/issues/70202
// TODO(dnfield): Support for gamepad as a distinct device type?
// https://github.com/flutter/flutter/issues/80472
binding_handler_delegate_->OnPointerDown(
scaled.x, scaled.y, kFlutterPointerDeviceKindMouse,
kDefaultPointerDeviceId,
FlutterPointerMouseButtons::kFlutterPointerButtonMousePrimary);
} else if ((buttons &
winrt::Windows::Gaming::Input::GamepadButtons::DPadLeft) ==
winrt::Windows::Gaming::Input::GamepadButtons::DPadLeft) {
binding_handler_delegate_->OnKey(
static_cast<int>(winrt::Windows::System::VirtualKey::Left), kScanLeft,
0x0100, 0, true, true);
} else if ((buttons &
winrt::Windows::Gaming::Input::GamepadButtons::DPadRight) ==
winrt::Windows::Gaming::Input::GamepadButtons::DPadRight) {
binding_handler_delegate_->OnKey(
static_cast<int>(winrt::Windows::System::VirtualKey::Right), kScanRight,
0x0100, 0, true, true);
} else if ((buttons &
winrt::Windows::Gaming::Input::GamepadButtons::DPadUp) ==
winrt::Windows::Gaming::Input::GamepadButtons::DPadUp) {
binding_handler_delegate_->OnKey(
static_cast<int>(winrt::Windows::System::VirtualKey::Up), kScanUp,
0x0100, 0, true, true);
} else if ((buttons &
winrt::Windows::Gaming::Input::GamepadButtons::DPadDown) ==
winrt::Windows::Gaming::Input::GamepadButtons::DPadDown) {
binding_handler_delegate_->OnKey(
static_cast<int>(winrt::Windows::System::VirtualKey::Down), kScanDown,
0x0100, 0, true, true);
}
}
void GamepadCursorWinUWP::OnGamepadButtonReleased(
winrt::Windows::Gaming::Input::GamepadButtons buttons) {
if ((buttons & winrt::Windows::Gaming::Input::GamepadButtons::A) ==
winrt::Windows::Gaming::Input::GamepadButtons::A) {
winrt::Windows::Foundation::Numerics::float3 scaled =
GetScaledInput(cursor_visual_.Offset());
// TODO(clarkezone) complete keyboard handling including
// system key (back), unicode handling, shortcut support,
// handling defered delivery, remove the need for action value.
// https://github.com/flutter/flutter/issues/70202
// TODO(dnfield): Support for gamepad as a distinct device type?
// https://github.com/flutter/flutter/issues/80472
binding_handler_delegate_->OnPointerUp(
scaled.x, scaled.y, kFlutterPointerDeviceKindMouse,
kDefaultPointerDeviceId,
FlutterPointerMouseButtons::kFlutterPointerButtonMousePrimary);
} else if ((buttons &
winrt::Windows::Gaming::Input::GamepadButtons::DPadLeft) ==
winrt::Windows::Gaming::Input::GamepadButtons::DPadLeft) {
binding_handler_delegate_->OnKey(
static_cast<int>(winrt::Windows::System::VirtualKey::Left), kScanLeft,
0x0100, 0, true, false);
} else if ((buttons &
winrt::Windows::Gaming::Input::GamepadButtons::DPadRight) ==
winrt::Windows::Gaming::Input::GamepadButtons::DPadRight) {
binding_handler_delegate_->OnKey(
static_cast<int>(winrt::Windows::System::VirtualKey::Right), kScanRight,
0x0100, 0, true, false);
} else if ((buttons &
winrt::Windows::Gaming::Input::GamepadButtons::DPadUp) ==
winrt::Windows::Gaming::Input::GamepadButtons::DPadUp) {
binding_handler_delegate_->OnKey(
static_cast<int>(winrt::Windows::System::VirtualKey::Up), kScanUp,
0x0100, 0, true, false);
} else if ((buttons &
winrt::Windows::Gaming::Input::GamepadButtons::DPadDown) ==
winrt::Windows::Gaming::Input::GamepadButtons::DPadDown) {
binding_handler_delegate_->OnKey(
static_cast<int>(winrt::Windows::System::VirtualKey::Down), kScanDown,
0x0100, 0, true, false);
}
}
void GamepadCursorWinUWP::OnGamepadControllersChanged() {
if (game_pad_->HasController()) {
if (!cursor_move_timer_) {
if (cursor_visual_ != nullptr) {
root_collection_.InsertAtTop(cursor_visual_);
}
StartGamepadTimer();
} else {
if (cursor_visual_ != nullptr) {
root_collection_.Remove(cursor_visual_);
}
StopGamepadTimer();
}
}
}
winrt::Windows::UI::Composition::Visual
GamepadCursorWinUWP::CreateCursorVisual() {
auto container = compositor_.CreateContainerVisual();
container.Offset(
{window_.Bounds().Width / 2, window_.Bounds().Height / 2, 1.0});
// size of the simulated mouse cursor
constexpr float size = 20;
constexpr float container_size = size + 10;
auto cursor_visual = compositor_.CreateShapeVisual();
cursor_visual.Size({container_size, container_size});
// compensate for overscan in cursor visual
cursor_visual.Offset({display_helper_->GetRenderTargetXOffset(),
display_helper_->GetRenderTargetYOffset(), 1.0});
winrt::Windows::UI::Composition::CompositionEllipseGeometry circle =
compositor_.CreateEllipseGeometry();
circle.Radius({size / 2, size / 2});
auto circleshape = compositor_.CreateSpriteShape(circle);
circleshape.FillBrush(
compositor_.CreateColorBrush(winrt::Windows::UI::Colors::Black()));
circleshape.StrokeBrush(
compositor_.CreateColorBrush(winrt::Windows::UI::Colors::White()));
circleshape.StrokeThickness(5.0);
circleshape.Offset({container_size / 2, container_size / 2});
cursor_visual.Shapes().Append(circleshape);
winrt::Windows::UI::Composition::Visual visual =
cursor_visual.as<winrt::Windows::UI::Composition::Visual>();
visual.AnchorPoint({0.5, 0.5});
container.Children().InsertAtTop(visual);
return container;
}
} // namespace flutter