blob: 237faadc02527667b87b2abf88fd6cbc2010119e [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/arc/input_overlay/ui/action_view.h"
#include <algorithm>
#include "ash/app_list/app_list_util.h"
#include "base/functional/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ash/arc/input_overlay/actions/action.h"
#include "chrome/browser/ash/arc/input_overlay/actions/input_element.h"
#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h"
#include "chrome/browser/ash/arc/input_overlay/constants.h"
#include "chrome/browser/ash/arc/input_overlay/display_overlay_controller.h"
#include "chrome/browser/ash/arc/input_overlay/touch_injector.h"
#include "chrome/browser/ash/arc/input_overlay/ui/action_label.h"
#include "chrome/browser/ash/arc/input_overlay/ui/arrow_container.h"
#include "chrome/browser/ash/arc/input_overlay/ui/reposition_controller.h"
#include "chrome/browser/ash/arc/input_overlay/ui/touch_point.h"
#include "chrome/browser/ash/arc/input_overlay/util.h"
#include "chrome/grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
namespace arc::input_overlay {
namespace {
constexpr int kAttachMargin = 8;
}
ActionView::ActionView(Action* action,
DisplayOverlayController* display_overlay_controller)
: views::View(),
action_(action),
display_overlay_controller_(display_overlay_controller) {}
ActionView::~ActionView() = default;
void ActionView::OnActionInputBindingUpdated() {
SetViewContent(BindingOption::kCurrent);
}
void ActionView::OnContentBoundsSizeChanged() {
SetPositionFromCenterPosition(action_->GetUICenterPosition());
}
void ActionView::SetDisplayMode(DisplayMode mode, ActionLabel* editing_label) {
// Set display mode for ActionLabel first and then other components update the
// layout according to ActionLabel.
if (!editing_label) {
for (arc::input_overlay::ActionLabel* label : labels_) {
label->SetDisplayMode(mode);
}
} else {
editing_label->SetDisplayMode(mode);
}
if (mode == DisplayMode::kView) {
display_mode_ = DisplayMode::kView;
if (!IsInputBound(action_->GetCurrentDisplayedInput())) {
SetVisible(false);
}
RemoveTouchPoint();
}
if (mode == DisplayMode::kEdit) {
display_mode_ = DisplayMode::kEdit;
AddTouchPoint();
if (!IsInputBound(*action_->current_input())) {
SetVisible(true);
}
}
}
void ActionView::SetPositionFromCenterPosition(
const gfx::PointF& center_position) {
DCHECK(touch_point_center_);
const int left =
std::max(0, (int)(center_position.x() - touch_point_center_->x()));
const int top =
std::max(0, (int)(center_position.y() - touch_point_center_->y()));
// SetPosition function needs the top-left position.
SetPosition(gfx::Point(left, top));
}
void ActionView::ShowFocusInfoMsg(std::string_view message, views::View* view) {
view->GetViewAccessibility().SetDescription(base::UTF8ToUTF16(message));
}
void ActionView::ChangeInputBinding(
Action* action,
ActionLabel* action_label,
std::unique_ptr<InputElement> input_element) {
display_overlay_controller_->OnInputBindingChange(action,
std::move(input_element));
SetDisplayMode(DisplayMode::kEdit, action_label);
}
void ActionView::RemoveNewState() {
for (arc::input_overlay::ActionLabel* label : labels_) {
label->RemoveNewState();
}
}
void ActionView::ApplyMousePressed(const ui::MouseEvent& event) {
reposition_controller_->OnMousePressed(event);
}
void ActionView::ApplyMouseDragged(const ui::MouseEvent& event) {
reposition_controller_->OnMouseDragged(event);
}
void ActionView::ApplyMouseReleased(const ui::MouseEvent& event) {
if (!reposition_controller_->OnMouseReleased(event)) {
ShowButtonOptionsMenu();
}
}
void ActionView::ApplyGestureEvent(ui::GestureEvent* event) {
if (!reposition_controller_->OnGestureEvent(event)) {
ShowButtonOptionsMenu();
}
}
bool ActionView::ApplyKeyPressed(const ui::KeyEvent& event) {
return reposition_controller_->OnKeyPressed(event);
}
bool ActionView::ApplyKeyReleased(const ui::KeyEvent& event) {
return reposition_controller_->OnKeyReleased(event);
}
void ActionView::ShowButtonOptionsMenu() {
DCHECK(display_overlay_controller_);
display_overlay_controller_->AddButtonOptionsMenuWidget(action_);
}
void ActionView::OnDraggingCallback() {
MayUpdateLabelPosition();
display_overlay_controller_->SetButtonOptionsMenuWidgetVisibility(
/*is_visible=*/false);
}
void ActionView::OnMouseDragEndCallback() {
action_->BindPosition(GetTouchCenterInWindow());
display_overlay_controller_->SetButtonOptionsMenuWidgetVisibility(
/*is_visible=*/true);
RecordInputOverlayActionReposition(
display_overlay_controller_->GetPackageName(),
RepositionType::kMouseDragRepostion,
display_overlay_controller_->GetWindowStateType());
}
void ActionView::OnGestureDragEndCallback() {
action_->BindPosition(GetTouchCenterInWindow());
display_overlay_controller_->SetButtonOptionsMenuWidgetVisibility(
/*is_visible=*/true);
RecordInputOverlayActionReposition(
display_overlay_controller_->GetPackageName(),
RepositionType::kTouchscreenDragRepostion,
display_overlay_controller_->GetWindowStateType());
}
void ActionView::OnKeyPressedCallback() {
MayUpdateLabelPosition();
}
void ActionView::OnKeyReleasedCallback() {
action_->BindPosition(GetTouchCenterInWindow());
RecordInputOverlayActionReposition(
display_overlay_controller_->GetPackageName(),
RepositionType::kKeyboardArrowKeyReposition,
display_overlay_controller_->GetWindowStateType());
}
void ActionView::SetTouchPointCenter(const gfx::Point& touch_point_center) {
touch_point_center_ = touch_point_center;
if (touch_point_) {
touch_point_->OnCenterPositionChanged(*touch_point_center_);
}
}
void ActionView::AddTouchPoint(ActionType action_type) {
if (touch_point_) {
return;
}
DCHECK(touch_point_center_);
touch_point_ = TouchPoint::Show(this, action_type, *touch_point_center_);
}
void ActionView::RemoveTouchPoint() {
if (!touch_point_) {
return;
}
RemoveChildViewT(touch_point_);
touch_point_ = nullptr;
}
gfx::Point ActionView::GetTouchCenterInWindow() const {
if (!touch_point_center_) {
auto point = action_->GetUICenterPosition();
return gfx::Point(point.x(), point.y());
}
auto pos = *touch_point_center_;
pos.Offset(origin().x(), origin().y());
return pos;
}
gfx::Point ActionView::CalculateAttachViewPositionInRootWindow(
const gfx::Rect& available_bounds,
const gfx::Point& window_content_origin,
ArrowContainer* attached_view) const {
auto origin_in_window = origin();
origin_in_window.Offset(window_content_origin.x(), window_content_origin.y());
// Check if `attached_view` can be placed on the left side or right side of
// this view. It depends on if there is enough space in its own display. If
// there is enough space on both sides, `can_attach_on_left` and
// `can_attach_on_right` are true.
bool can_attach_on_left = false, can_attach_on_right = false;
const auto attached_view_size = attached_view->GetPreferredSize();
// Width of `attached_view` including the margin of this view.
const int attached_view_width_extra =
kAttachMargin + attached_view_size.width();
if (origin_in_window.x() + width() + attached_view_width_extra <=
available_bounds.width()) {
can_attach_on_right = true;
}
if (origin_in_window.x() - attached_view_width_extra >= 0) {
can_attach_on_left = true;
}
// Calculate the position of x.
int x = 0;
const auto touch_center_in_window = GetTouchCenterInWindow();
// If the display space is not considered, the position of `attached_view` is
// toward to the center of the game window, which means if this view is on the
// left of the window, then `attached_view` should be placed on the right side
// of this view.
bool should_attach_on_right =
touch_center_in_window.x() < parent()->size().width() / 2.0;
// `final_attach_on_left` is the final decision based on
// `should_attach_on_right`, `can_attach_on_left` and `can_attach_on_right`.
bool final_attach_on_left = false;
if (should_attach_on_right) {
if (!can_attach_on_right && can_attach_on_left) {
// Attach `attached_view` on the left side of this view.
x = origin_in_window.x() - attached_view_width_extra;
final_attach_on_left = true;
} else {
// Attach `attached_view` on the right side of this view.
x = origin_in_window.x() + width() + kAttachMargin;
if (x + attached_view_size.width() > available_bounds.width()) {
x = available_bounds.width() - attached_view_size.width();
}
}
} else {
if (!can_attach_on_left && can_attach_on_right) {
// Attach `attached_view` on the right side of this view.
x = origin_in_window.x() + width() + kAttachMargin;
} else {
// Attach `attached_view` on the left side of this view.
x = std::max(0, origin_in_window.x() - attached_view_width_extra);
final_attach_on_left = true;
}
}
attached_view->SetArrowOnLeft(!final_attach_on_left);
// Check y position to make sure that `attached_view` shows completely inside
// of the display.
int y = std::max(0, window_content_origin.y() + touch_center_in_window.y() -
attached_view_size.height() / 2);
y = std::min(y, available_bounds.height() - attached_view_size.height());
attached_view->SetArrowVerticalOffset(
touch_center_in_window.y() -
(y - window_content_origin.y() + attached_view_size.height() / 2));
return gfx::Point(x, y);
}
void ActionView::AddedToWidget() {
SetRepositionController();
}
void ActionView::SetRepositionController() {
if (reposition_controller_) {
return;
}
reposition_controller_ = std::make_unique<RepositionController>(this);
reposition_controller_->set_dragging_callback(base::BindRepeating(
&ActionView::OnDraggingCallback, base::Unretained(this)));
reposition_controller_->set_mouse_drag_end_callback(base::BindRepeating(
&ActionView::OnMouseDragEndCallback, base::Unretained(this)));
reposition_controller_->set_gesture_drag_end_callback(base::BindRepeating(
&ActionView::OnGestureDragEndCallback, base::Unretained(this)));
reposition_controller_->set_key_pressed_callback(base::BindRepeating(
&ActionView::OnKeyPressedCallback, base::Unretained(this)));
reposition_controller_->set_key_released_callback(base::BindRepeating(
&ActionView::OnKeyReleasedCallback, base::Unretained(this)));
}
BEGIN_METADATA(ActionView)
END_METADATA
} // namespace arc::input_overlay