| // 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 |