1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101

ash / system / accessibility / facegaze_bubble_controller.cc [blame]

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/system/accessibility/facegaze_bubble_controller.h"

#include "ash/system/accessibility/facegaze_bubble_view.h"
#include "ash/wm/collision_detection/collision_detection_utils.h"
#include "base/functional/bind.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/widget/widget.h"

namespace ash {

namespace {
constexpr base::TimeDelta kShowTimeout = base::Seconds(1);
constexpr int kMarginFromTopDip = 8;
}  // namespace

FaceGazeBubbleController::FaceGazeBubbleController() = default;

FaceGazeBubbleController::~FaceGazeBubbleController() {
  show_timer_.Stop();
  if (widget_ && !widget_->IsClosed()) {
    widget_->CloseNow();
  }
}

void FaceGazeBubbleController::OnViewIsDeleting(views::View* observed_view) {
  if (observed_view != facegaze_bubble_view_) {
    return;
  }

  show_timer_.Stop();
  facegaze_bubble_view_->views::View::RemoveObserver(this);
  facegaze_bubble_view_ = nullptr;
  widget_ = nullptr;
}

void FaceGazeBubbleController::UpdateBubble(const std::u16string& text,
                                            bool is_warning) {
  MaybeInitialize();
  Update(text, is_warning);
  if (!show_timer_.IsRunning()) {
    widget_->Show();
  }
}

void FaceGazeBubbleController::MaybeInitialize() {
  if (widget_) {
    return;
  }

  facegaze_bubble_view_ = new FaceGazeBubbleView(base::BindRepeating(
      &FaceGazeBubbleController::OnMouseEntered, GetWeakPtr()));
  facegaze_bubble_view_->views::View::AddObserver(this);

  widget_ =
      views::BubbleDialogDelegateView::CreateBubble(facegaze_bubble_view_);
  CollisionDetectionUtils::MarkWindowPriorityForCollisionDetection(
      widget_->GetNativeWindow(),
      CollisionDetectionUtils::RelativePriority::kFaceGazeBubble);
}

void FaceGazeBubbleController::Update(const std::u16string& text,
                                      bool is_warning) {
  if (!facegaze_bubble_view_) {
    return;
  }

  facegaze_bubble_view_->Update(text, is_warning);

  const gfx::Rect primary_work_area =
      display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
  const gfx::Size work_area_size = primary_work_area.size();
  const gfx::Size bubble_size = facegaze_bubble_view_->size();

  // The bubble should be centered at the top of the screen, factoring in other
  // UI elements such as the ChromeVox panel. Note that the work area may not
  // always start at (0, 0) so we need to factor in the starting point of the
  // work area.
  int center = (work_area_size.width() / 2) - (bubble_size.width() / 2) +
               primary_work_area.x();
  int top = primary_work_area.y() + kMarginFromTopDip;
  facegaze_bubble_view_->SetAnchorRect(gfx::Rect(center, top, 0, 0));
}

void FaceGazeBubbleController::OnMouseEntered() {
  widget_->Hide();
  show_timer_.Start(FROM_HERE, kShowTimeout,
                    base::BindRepeating(&FaceGazeBubbleController::OnShowTimer,
                                        GetWeakPtr()));
}

void FaceGazeBubbleController::OnShowTimer() {
  widget_->Show();
}

}  // namespace ash