blob: 88adea0da3e14bd7f5175242e1c48b015ba88ed7 [file] [log] [blame]
// Copyright 2023 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/target_view.h"
#include <algorithm>
#include "base/notreached.h"
#include "cc/paint/paint_flags.h"
#include "chrome/browser/ash/arc/input_overlay/actions/action.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/touch_point.h"
#include "chrome/browser/ash/arc/input_overlay/ui/ui_utils.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/color/color_provider.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/widget/widget.h"
namespace arc::input_overlay {
namespace {
constexpr int kDashedLineStrokeWidth = 1;
constexpr int kDashLength = 2;
constexpr int kDashGap = 4;
constexpr int kCircleRingStroke = 3;
constexpr int kActionTapCircleRadius = 37;
constexpr int kActionTapCircleRingRadius = 29; // 32 - inside stroke 3
constexpr int kActionMoveCircleRadius = 69;
constexpr int kActionMoveCircleRingRadius = 61; // 64 - inside stroke 3
// Returns true if `val` is inside of the circle with `radius` and `center`.
bool InsideOfCircle(const int radius, const int center, const int val) {
return val >= center - radius && val < center + radius;
}
// Clamps `start` and `end` oustside of the circle with `radius` and `center`.
void ClampDashLineInCircle(const int radius,
const int center,
const int limit,
int& start,
int& end) {
if (InsideOfCircle(radius, center, start) &&
InsideOfCircle(radius, center, end)) {
// Whole dash line segment of `kDashLength` is inside of the circle.
start = end = 0;
} else if (InsideOfCircle(radius, center, start)) {
// `start` is inside of the circle.
start = center + radius;
} else if (InsideOfCircle(radius, center, end)) {
// `end` is inside of the circle.
end = center - radius;
}
start = std::clamp(start, 0, limit);
end = std::clamp(end, 0, limit);
}
} // namespace
TargetView::TargetView(DisplayOverlayController* controller,
ActionType action_type)
: controller_(controller), action_type_(action_type) {
const auto* touch_injector = controller_->touch_injector();
DCHECK(touch_injector);
const auto& bounds = touch_injector->content_bounds();
center_.set_x(bounds.width() / 2);
center_.set_y(bounds.height() / 2);
SetFocusBehavior(FocusBehavior::ALWAYS);
GetViewAccessibility().SetRole(ax::mojom::Role::kPane);
GetViewAccessibility().SetName(
l10n_util::GetStringUTF16(IDS_INPUT_OVERLAY_BUTTON_PLACEMENT_A11Y_LABEL));
}
TargetView::~TargetView() = default;
void TargetView::UpdateWidgetBounds() {
auto* widget = GetWidget();
DCHECK(widget);
controller_->UpdateWidgetBoundsInRootWindow(
widget, controller_->touch_injector()->content_bounds());
}
gfx::Rect TargetView::GetTargetCircleBounds() const {
gfx::Rect bounds = gfx::Rect(center_.x(), center_.y(), 0, 0);
bounds.Outset(GetCircleRadius());
return bounds;
}
int TargetView::GetCircleRadius() const {
switch (action_type_) {
case ActionType::TAP:
return kActionTapCircleRadius;
case ActionType::MOVE:
return kActionMoveCircleRadius;
default:
NOTREACHED();
}
}
int TargetView::GetCircleRingRadius() const {
switch (action_type_) {
case ActionType::TAP:
return kActionTapCircleRingRadius;
case ActionType::MOVE:
return kActionMoveCircleRingRadius;
default:
NOTREACHED();
}
}
int TargetView::GetPadding() const {
return TouchPoint::GetEdgeLength(action_type_) / 2;
}
void TargetView::ClampCenter() {
const int padding = GetPadding();
const auto& view_size = size();
center_.set_x(std::clamp(center_.x(), /*lo=*/padding,
/*hi=*/view_size.width() - padding));
center_.set_y(std::clamp(center_.y(), /*lo=*/padding,
/*hi=*/view_size.height() - padding));
}
void TargetView::OnCenterChanged() {
ClampCenter();
SchedulePaint();
controller_->UpdateButtonPlacementNudgeAnchorRect();
}
void TargetView::MoveCursorToViewCenter() {
auto* widget = GetWidget();
DCHECK(widget);
auto* window = widget->GetNativeWindow();
DCHECK(window);
window->MoveCursorTo(bounds().CenterPoint());
}
void TargetView::VisibilityChanged(views::View* starting_from,
bool is_visible) {
if (is_visible) {
UpdateWidgetBounds();
RequestFocus();
MoveCursorToViewCenter();
}
}
void TargetView::OnGestureEvent(ui::GestureEvent* event) {
auto type = event->type();
center_ = event->location();
OnCenterChanged();
event->SetHandled();
// When the gesture event is released, add a new action.
if (type == ui::EventType::kGestureScrollEnd ||
type == ui::EventType::kScrollFlingStart ||
type == ui::EventType::kGesturePinchEnd ||
type == ui::EventType::kGestureEnd) {
controller_->AddNewAction(action_type_, center_);
}
}
bool TargetView::OnKeyPressed(const ui::KeyEvent& event) {
const auto key_code = event.key_code();
// Exit the button placement mode when key `esc` is pressed.
if (key_code == ui::VKEY_ESCAPE) {
controller_->ExitButtonPlaceMode(/*is_action_added=*/false);
return true;
}
// Continue to add new action if key `enter` is pressed.
if (key_code == ui::VKEY_RETURN) {
controller_->AddNewAction(action_type_, center_);
return true;
}
// Update `center_` and repaint if arrow keys are pressed.
if (OffsetPositionByArrowKey(key_code, center_)) {
OnCenterChanged();
return true;
}
return false;
}
void TargetView::OnMouseMoved(const ui::MouseEvent& event) {
center_ = event.location();
OnCenterChanged();
}
bool TargetView::OnMousePressed(const ui::MouseEvent& event) {
// Considered as consumed. If the mouse is clicked, a new action is added when
// the mouse button is released.
return true;
}
void TargetView::OnMouseReleased(const ui::MouseEvent& event) {
controller_->AddNewAction(action_type_, center_);
}
void TargetView::OnPaintBackground(gfx::Canvas* canvas) {
cc::PaintFlags flags;
flags.setAntiAlias(true);
const auto* color_provider = GetColorProvider();
const int circle_ring_radius = GetCircleRingRadius();
const int circle_ring_overall_radius = circle_ring_radius + kCircleRingStroke;
const int center_x = center_.x();
const int center_y = center_.y();
// Draw background circle.
flags.setStyle(cc::PaintFlags::kFill_Style);
flags.setColor(
SkColorSetA(color_provider->GetColor(cros_tokens::kCrosSysPrimary),
GetAlpha(/*percent=*/0.8f)));
canvas->DrawCircle(center_, GetCircleRadius(), flags);
// Draw dashed crossed lines.
flags.setStyle(cc::PaintFlags::kStroke_Style);
flags.setStrokeWidth(kDashedLineStrokeWidth);
flags.setStrokeCap(cc::PaintFlags::Cap::kRound_Cap);
flags.setColor(color_provider->GetColor(cros_tokens::kCrosSysWhite));
const auto& view_size = size();
const int width = view_size.width();
const int height = view_size.height();
// Draw the horizontal dashed line.
for (int x = 0; x < width; x += kDashGap + kDashLength) {
int start = x;
int end = x + kDashLength;
ClampDashLineInCircle(circle_ring_overall_radius, center_x, width, start,
end);
if (start < end) {
canvas->DrawLine(gfx::Point(start, center_y), gfx::Point(end, center_y),
flags);
}
}
// Draw the vertical dashed line.
for (int y = 0; y < height; y += kDashGap + kDashLength) {
int start = y;
int end = y + kDashLength;
ClampDashLineInCircle(circle_ring_overall_radius, center_y, height, start,
end);
if (start < end) {
canvas->DrawLine(gfx::Point(center_x, start), gfx::Point(center_x, end),
flags);
}
}
// Draw the white circle ring.
flags.setStyle(cc::PaintFlags::kStroke_Style);
flags.setColor(color_provider->GetColor(cros_tokens::kCrosSysWhite));
flags.setStrokeWidth(kCircleRingStroke);
canvas->DrawCircle(center_, circle_ring_radius, flags);
// Draw the touch point.
TouchPoint::DrawTouchPoint(canvas, color_provider, action_type_,
UIState::kDefault, center_);
}
void TargetView::OnThemeChanged() {
views::View::OnThemeChanged();
// Repaint to update colors.
SchedulePaint();
}
BEGIN_METADATA(TargetView)
END_METADATA
} // namespace arc::input_overlay