chromium/ash/annotator/annotator_controller.cc

// 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/annotator/annotator_controller.h"

#include "ash/annotator/annotation_source_watcher.h"
#include "ash/annotator/annotation_tray.h"
#include "ash/annotator/annotations_overlay_controller.h"
#include "ash/annotator/annotator_metrics.h"
#include "ash/capture_mode/capture_mode_controller.h"
#include "ash/projector/projector_metrics.h"
#include "ash/public/cpp/annotator/annotations_overlay_view.h"
#include "ash/public/cpp/annotator/annotator_tool.h"
#include "ash/root_window_controller.h"
#include "ash/system/status_area_widget.h"
#include "ash/webui/annotator/public/cpp/annotator_client.h"
#include "base/check.h"
#include "ui/aura/window.h"
#include "ui/gfx/geometry/rect_f.h"

namespace ash {
namespace {
AnnotatorMarkerColor GetMarkerColorForMetrics(SkColor color) {
  switch (color) {
    case kAnnotatorMagentaPenColor:
      return AnnotatorMarkerColor::kMagenta;
    case kAnnotatorBluePenColor:
      return AnnotatorMarkerColor::kBlue;
    case kAnnotatorRedPenColor:
      return AnnotatorMarkerColor::kRed;
    case kAnnotatorYellowPenColor:
      return AnnotatorMarkerColor::kYellow;
  }
  return AnnotatorMarkerColor::kMaxValue;
}

AnnotationTray* GetAnnotationTrayForRoot(aura::Window* root) {
  // It may happen that root is nullptr. This may happen in the event that
  // the annotation tray is hidden before the canvas finishes its
  // initialization.
  if (!root) {
    return nullptr;
  }

  DCHECK(root->IsRootWindow());

  // Annotating can end when a display being fullscreen-captured gets removed,
  // in this case, we don't need to hide the button.
  if (root->is_destroying()) {
    return nullptr;
  }

  // Can be null while shutting down.
  auto* root_window_controller = RootWindowController::ForWindow(root);
  if (!root_window_controller) {
    return nullptr;
  }

  auto* annotation_tray =
      root_window_controller->GetStatusAreaWidget()->annotation_tray();
  DCHECK(annotation_tray);
  return annotation_tray;
}

void SetAnnotationTrayVisibility(aura::Window* root, bool visible) {
  if (auto* annotation_tray = GetAnnotationTrayForRoot(root)) {
    annotation_tray->SetVisiblePreferred(visible);
  }
}
}  // namespace

AnnotatorController::AnnotatorController() {
  annotation_source_watcher_ = std::make_unique<AnnotationSourceWatcher>(this);
}

AnnotatorController::~AnnotatorController() {
  annotation_source_watcher_.reset();
  annotations_overlay_controller_.reset();
  client_ = nullptr;
  current_root_ = nullptr;
}

void AnnotatorController::SetAnnotatorTool(const AnnotatorTool& tool) {
  DCHECK(client_);
  client_->SetTool(tool);
  RecordMarkerColorMetrics(GetMarkerColorForMetrics(tool.color));
}

void AnnotatorController::ResetTools() {
  if (annotator_enabled_) {
    DCHECK(client_);
    ToggleAnnotatorCanvas();
    annotator_enabled_ = false;
    client_->Clear();
  }
}

void AnnotatorController::RegisterView(aura::Window* new_root) {
  // Make sure the annotator tray is only visible on one root window.
  // TODO(b/342104047): Remove this check when annotator starts being used
  // outside of the capture mode.
  if (current_root_) {
    UnregisterView(current_root_);
  }
  current_root_ = new_root;
  // Show the tray icon.
  SetAnnotationTrayVisibility(current_root_, /*visible=*/true);
}

void AnnotatorController::UnregisterView(aura::Window* window) {
  DCHECK_EQ(current_root_, window);
  if (auto* annotation_tray = GetAnnotationTrayForRoot(current_root_)) {
    annotation_tray->HideAnnotationTray();
  }
  current_root_ = nullptr;
}

void AnnotatorController::UpdateRootView(aura::Window* new_root) {
  // Do nothing if the root window is the same.
  if (new_root == current_root_) {
    return;
  }
  UnregisterView(current_root_);
  RegisterView(new_root);
  current_root_ = new_root;
  if (GetAnnotatorAvailability()) {
    UpdateTrayEnabledState();
  }
}

void AnnotatorController::EnableAnnotatorTool() {
  if (!annotator_enabled_ && annotations_overlay_controller_) {
    ToggleAnnotatorCanvas();
    annotator_enabled_ = !annotator_enabled_;
    // TODO(b/342104047): Decouple from projector metrics.
    RecordToolbarMetrics(ProjectorToolbar::kMarkerTool);
  }
}

void AnnotatorController::DisableAnnotator() {
  ResetTools();
  if (current_root_) {
    UnregisterView(current_root_);
  }
  annotations_overlay_controller_.reset();
  canvas_initialized_state_.reset();
}

void AnnotatorController::CreateAnnotationOverlayForWindow(
    aura::Window* window,
    std::optional<gfx::Rect> partial_region_bounds) {
  annotations_overlay_controller_ =
      std::make_unique<AnnotationsOverlayController>(window,
                                                     partial_region_bounds);
}

void AnnotatorController::SetToolClient(AnnotatorClient* client) {
  client_ = client;
}

bool AnnotatorController::GetAnnotatorAvailability() const {
  return canvas_initialized_state_.value_or(false);
}

void AnnotatorController::OnCanvasInitialized(bool success) {
  canvas_initialized_state_ = success;
  UpdateTrayEnabledState();
  if (on_canvas_initialized_callback_for_test_) {
    std::move(on_canvas_initialized_callback_for_test_).Run();
  }
}

void AnnotatorController::ToggleAnnotationTray() {
  if (auto* annotation_tray = GetAnnotationTrayForRoot(current_root_)) {
    annotation_tray->ToggleAnnotator();
  }
}

// Callback indicating availability of undo and redo functionalities.
void AnnotatorController::OnUndoRedoAvailabilityChanged(bool undo_available,
                                                        bool redo_available) {
  // TODO(b/198184362): Reflect undo and redo buttons availability
  // on the annotator tray.
}

std::unique_ptr<AnnotationsOverlayView>
AnnotatorController::CreateAnnotationsOverlayView() const {
  return client_->CreateAnnotationsOverlayView();
}

void AnnotatorController::UpdateTrayEnabledState() {
  if (auto* annotation_tray = GetAnnotationTrayForRoot(current_root_)) {
    annotation_tray->SetTrayEnabled(GetAnnotatorAvailability());
  }
}

void AnnotatorController::ToggleAnnotatorCanvas() {
  auto* capture_mode_controller = CaptureModeController::Get();
  // TODO(b/342104047): This check is necessary as long as we only toggle
  // annotator from Projector. Once we start using the annotator outside of
  // Projector, we should remove the check.
  if (capture_mode_controller->is_recording_in_progress()) {
    annotations_overlay_controller_->Toggle();
  }
}

}  // namespace ash