chromium/ash/accessibility/magnifier/fullscreen_magnifier_controller.h

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

#ifndef ASH_ACCESSIBILITY_MAGNIFIER_FULLSCREEN_MAGNIFIER_CONTROLLER_H_
#define ASH_ACCESSIBILITY_MAGNIFIER_FULLSCREEN_MAGNIFIER_CONTROLLER_H_

#include <map>
#include <memory>

#include "ash/ash_export.h"
#include "ash/public/cpp/accessibility_controller_enums.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "ui/aura/window_observer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/events/event_handler.h"
#include "ui/events/event_rewriter.h"
#include "ui/events/gestures/gesture_types.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"

namespace aura {
class Window;
}  // namespace aura

namespace gfx {
class Transform;
}

namespace ui {
class GestureProviderAura;
}  // namespace ui

namespace ash {

// FullscreenMagnifierController implements GestureConsumer as it has its own
// GestureProvider to recognize gestures with screen coordinates of touches.
// Logical coordinates of touches cannot be used as they are changed with
// viewport change: scroll, zoom.
// FullscreenMagnifierController implements EventRewriter to see and rewrite
// touch events. Once the controller detects two fingers pinch or scroll, it
// starts consuming all touch events not to confuse an app or a browser on the
// screen. It needs to rewrite events to dispatch touch cancel events.
class ASH_EXPORT FullscreenMagnifierController
    : public ui::EventHandler,
      public ui::ImplicitAnimationObserver,
      public aura::WindowObserver,
      public ui::GestureConsumer,
      public ui::EventRewriter {
 public:
  enum ScrollDirection {
    SCROLL_NONE,
    SCROLL_LEFT,
    SCROLL_RIGHT,
    SCROLL_UP,
    SCROLL_DOWN
  };

  FullscreenMagnifierController();
  FullscreenMagnifierController(const FullscreenMagnifierController&) = delete;
  FullscreenMagnifierController& operator=(
      const FullscreenMagnifierController&) = delete;
  ~FullscreenMagnifierController() override;

  void set_mouse_following_mode(
      MagnifierMouseFollowingMode mouse_following_mode) {
    mouse_following_mode_ = mouse_following_mode;
  }

  MagnifierMouseFollowingMode mouse_following_mode() const {
    return mouse_following_mode_;
  }

  // Enables (or disables if |enabled| is false) screen magnifier feature.
  void SetEnabled(bool enabled);

  // Returns if the screen magnifier is enabled or not.
  bool IsEnabled() const;

  // Sets the magnification ratio. 1.0f means no magnification.
  void SetScale(float scale, bool animate);

  // Returns the current magnification ratio.
  float GetScale() const { return scale_; }

  // Maps the current scale value to an index in the range between the minimum
  // and maximum scale values, and steps up or down the scale depending on the
  // value of |delta_index|.
  void StepToNextScaleValue(int delta_index);

  // Set the top-left point of the magnification window.
  void MoveWindow(int x, int y, bool animate);
  void MoveWindow(const gfx::Point& point, bool animate);

  // Returns the current top-left point of the magnification window.
  gfx::Point GetWindowPosition() const;

  void SetScrollDirection(ScrollDirection direction);

  // Returns the viewport (i.e. the current visible window)'s Rect in root
  // window coordinates.
  gfx::Rect GetViewportRect() const;

  // Centers the viewport around the given point in screen coordinates.
  void CenterOnPoint(const gfx::Point& point_in_screen);

  // Move |rect_in_screen| within the magnifier viewport. If |rect_in_screen| is
  // already completely within the viewport, do nothing. If any edge of
  // |rect_in_screen| is outside the viewport (e.g. if rect is larger than or
  // extends partially beyond the viewport), center the overflowing dimensions
  // of the viewport on center of |rect_in_screen| (e.g. center viewport
  // vertically if |rect| extends beyond bottom of screen). Called from
  // Accessibility Common extension. Called from Accessibility Common extension.
  void HandleMoveMagnifierToRect(const gfx::Rect& rect_in_screen);

  // Switch the magnified root window to |new_root_window|. This does following:
  //  - Unzoom the current root_window.
  //  - Zoom the given new root_window |new_root_window|.
  //  - Switch the target window from current window to |new_root_window|.
  void SwitchTargetRootWindow(aura::Window* new_root_window,
                              bool redraw_original_root);

  // Returns the magnification transformation for the root window. If
  // magnification is disabled, return an empty Transform.
  gfx::Transform GetMagnifierTransform() const;

  // Returns the last mouse cursor (or last touched) location.
  gfx::Point GetPointOfInterestForTesting() {
    return point_of_interest_in_root_;
  }

  // Returns true if magnifier is still on animation for moving viewport.
  bool IsOnAnimationForTesting() const { return is_on_animation_; }

  // Returns the current number of touch points.
  int32_t GetTouchPointsForTesting() const { return touch_points_; }

  void set_cursor_moved_callback_for_testing(
      base::RepeatingCallback<void(const gfx::Point&)> callback) {
    cursor_moved_callback_for_testing_ = std::move(callback);
  }

 private:
  friend class FullscreenMagnifierControllerTest;
  class GestureProviderClient;

  // ui::ImplicitAnimationObserver overrides:
  void OnImplicitAnimationsCompleted() override;

  // aura::WindowObserver overrides:
  void OnWindowDestroying(aura::Window* root_window) override;
  void OnWindowBoundsChanged(aura::Window* window,
                             const gfx::Rect& old_bounds,
                             const gfx::Rect& new_bounds,
                             ui::PropertyChangeReason reason) override;

  // ui::EventHandler overrides:
  void OnMouseEvent(ui::MouseEvent* event) override;
  void OnScrollEvent(ui::ScrollEvent* event) override;
  void OnTouchEvent(ui::TouchEvent* event) override;

  // ui::EventRewriter overrides:
  ui::EventDispatchDetails RewriteEvent(
      const ui::Event& event,
      const Continuation continuation) override;

  // ui::GestureConsumer:
  const std::string& GetName() const override;

  // Redraws the magnification window with the given origin position and the
  // given scale. Returns true if the window is changed; otherwise, false.
  // These methods should be called internally just after the scale and/or
  // the position are changed to redraw the window.
  bool Redraw(const gfx::PointF& position_in_pixels, float scale, bool animate);

  // Redraws the magnification window with the given origin position in dip and
  // the given scale. Returns true if the window is changed; otherwise, false.
  // The last two parameters specify the animation duration and tween type.
  // If |animation_in_ms| is zero, there will be no animation, and |tween_type|
  // will be ignored.
  bool RedrawDIP(const gfx::PointF& position_in_dip,
                 float scale,
                 int animation_in_ms,
                 gfx::Tween::Type tween_type);

  // 1) If the screen is scrolling (i.e. animating) and should scroll further,
  // it does nothing.
  // 2) If the screen is scrolling (i.e. animating) and the direction is NONE,
  // it stops the scrolling animation.
  // 3) If the direction is set to value other than NONE, it starts the
  // scrolling/ animation towards that direction.
  void StartOrStopScrollIfNecessary();

  // Redraw with the given zoom scale keeping the mouse cursor location. In
  // other words, zoom (or unzoom) centering around the cursor.
  // Ignore mouse position change after redrawing if |ignore_mouse_change| is
  // true.
  void RedrawKeepingMousePosition(float scale,
                                  bool animate,
                                  bool ignore_mouse_change);

  // Takes mouse root `location` in floating-point DIP. Note at higher zoom
  // levels, the floating point values matter more, because the ratio of px to
  // DIP increases.
  void OnMouseMove(const gfx::PointF& location);

  // Move the mouse cursot to the given point. Actual move will be done when
  // the animation is completed. This should be called after animation is
  // started.
  void AfterAnimationMoveCursorTo(const gfx::Point& location);

  // Returns if the magnification scale is 1.0 or not (larger then 1.0).
  bool IsMagnified() const;

  // Returns the rect of the magnification window.
  gfx::RectF GetWindowRectDIP(float scale) const;

  // Returns the size of the root window.
  gfx::Size GetHostSizeDIP() const;

  // Correct the given scale value if necessary.
  void ValidateScale(float* scale);

  // Process pending gestures in |gesture_provider_|. This method returns true
  // if the controller needs to cancel existing touches.
  bool ProcessGestures();

  // Moves the viewport when |point| is located within
  // |x_margin| and |y_margin| to the edge of the visible
  // window region. The viewport will be moved so that the |point| will be
  // moved to the point where it has |x_margin| and |y_margin|
  // to the edge of the visible region if possible (less if the mouse is closer
  // to the edge of the screen). If |reduce_bottom_margin| is true,
  // then a reduced value will be used as the |y_margin| and
  // |y_target_margin| for the bottom edge.
  void MoveMagnifierWindowFollowPoint(const gfx::Point& point,
                                      int x_margin,
                                      int y_margin,
                                      bool reduce_bottom_margin);

  // Moves the viewport to center |point| in magnifier screen.
  void MoveMagnifierWindowCenterPoint(const gfx::Point& point);

  // Moves the viewport so that |rect| is fully visible. If |rect| is larger
  // than the viewport horizontally or vertically, the viewport will be moved
  // to center the |rect| in that dimension.
  void MoveMagnifierWindowFollowRect(const gfx::Rect& rect);

  // Moves the cursor to the given location in the root window.
  void MoveCursorTo(const gfx::Point& root_location);

  // Target root window. This must not be NULL.
  raw_ptr<aura::Window> root_window_;

  // True if the magnified window is currently animating a change. Otherwise,
  // false.
  bool is_on_animation_ = false;

  bool is_enabled_ = false;

  bool keep_focus_centered_ = false;

  // The current mouse following mode (e.g. continuous, centered, edge).
  MagnifierMouseFollowingMode mouse_following_mode_ =
      MagnifierMouseFollowingMode::kEdge;

  // True if the cursor needs to move the given position after the animation
  // will be finished. When using this, set |position_after_animation_| as well.
  bool move_cursor_after_animation_ = false;

  // Stores the position of cursor to be moved after animation.
  gfx::Point position_after_animation_;

  // Stores the last mouse cursor (or last touched) location. This value is
  // used on zooming to keep this location visible.
  gfx::Point point_of_interest_in_root_;

  // Current scale, origin (left-top) position of the magnification window.
  float scale_;
  gfx::PointF origin_;

  float original_scale_;
  gfx::PointF original_origin_;

  ScrollDirection scroll_direction_ = SCROLL_NONE;

  // If true, FullscreenMagnifierController consumes all touch events.
  bool consume_touch_event_ = false;

  // Number of touch points on the screen.
  int32_t touch_points_ = 0;

  // Map for holding EventType::kTouchPress events. Those events are used to
  // dispatch EventType::kTouchCancelled events. Events will be removed from
  // this map when press events are cancelled, i.e. size of this map can be
  // different from number of touches on the screen. Key is pointer id.
  std::map<int32_t, std::unique_ptr<ui::TouchEvent>> press_event_map_;

  std::unique_ptr<GestureProviderClient> gesture_provider_client_;

  // MagnificationCotroller owns its GestureProvider to detect gestures with
  // screen coordinates of touch events. As FullscreenMagnifierController
  // changes zoom level and moves viewport, logical coordinates of touches
  // cannot be used for gesture detection as they are changed if the controller
  // reacts to gestures.
  std::unique_ptr<ui::GestureProviderAura> gesture_provider_;

  // Most recent caret position in |root_window_| coordinates.
  gfx::Point caret_point_;

  // Flag to draw a preview box around magnifier viewport area instead of
  // magnifying the screen for debugging.
  bool magnifier_debug_draw_rect_ = false;

  // Called every time MoveCursorTo is called, when set in tests.
  base::RepeatingCallback<void(const gfx::Point&)>
      cursor_moved_callback_for_testing_;
};

}  // namespace ash

#endif  // ASH_ACCESSIBILITY_MAGNIFIER_FULLSCREEN_MAGNIFIER_CONTROLLER_H_