|  | // Copyright 2019 The Fuchsia Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #ifndef SRC_UI_A11Y_LIB_MAGNIFIER_MAGNIFIER_H_ | 
|  | #define SRC_UI_A11Y_LIB_MAGNIFIER_MAGNIFIER_H_ | 
|  |  | 
|  | #include <fuchsia/accessibility/cpp/fidl.h> | 
|  | #include <fuchsia/ui/scenic/cpp/fidl.h> | 
|  | #include <lib/async/cpp/task.h> | 
|  | #include <lib/zx/time.h> | 
|  |  | 
|  | #include <memory> | 
|  |  | 
|  | #include "src/lib/callback/scoped_task_runner.h" | 
|  | #include "src/lib/fxl/memory/weak_ptr.h" | 
|  | #include "src/lib/ui/input/gesture_detector.h" | 
|  | #include "src/ui/a11y/lib/gesture_manager/arena/contest_member.h" | 
|  | #include "src/ui/a11y/lib/gesture_manager/arena/recognizer.h" | 
|  | #include "src/ui/lib/glm_workaround/glm_workaround.h" | 
|  |  | 
|  | namespace a11y { | 
|  |  | 
|  | class Magnifier : public fuchsia::accessibility::Magnifier, | 
|  | public GestureRecognizer, | 
|  | input::GestureDetector::Delegate { | 
|  | public: | 
|  | // Max time between tap begins in a trigger gesture. | 
|  | static constexpr zx::duration kTriggerMaxDelay = zx::msec(400); | 
|  | // Time a trigger needs to be held in place before it signifies temporary zoom | 
|  | // rather than a toggle. Moving the pointer also transitions to a temporary | 
|  | // zoom. | 
|  | static constexpr zx::duration kTemporaryZoomHold = zx::msec(500); | 
|  | // Transition over .2 s @ 60 fps. | 
|  | static constexpr zx::duration kTransitionPeriod = zx::msec(200); | 
|  | static constexpr float kTransitionRate = 1 / (kTransitionPeriod.to_msecs() * .060f); | 
|  | static constexpr float kDragThreshold = 1.f / 16;  // NDC | 
|  | static constexpr float kMinScale = 2, kMaxScale = 20; | 
|  | static constexpr float kDefaultScale = 4; | 
|  |  | 
|  | Magnifier(); | 
|  | ~Magnifier() override; | 
|  |  | 
|  | // |fuchsia::accessibility::Magnifier| | 
|  | void RegisterHandler( | 
|  | fidl::InterfaceHandle<fuchsia::accessibility::MagnificationHandler> handler) override; | 
|  |  | 
|  | // Used when magnification is toggled off, to restore the presentation to an umagnified state. | 
|  | void ZoomOutIfMagnified(); | 
|  |  | 
|  | // |GestureRecognizer| | 
|  | void OnWin() override; | 
|  | // |GestureRecognizer| | 
|  | void OnDefeat() override; | 
|  | // |GestureRecognizer| | 
|  | void OnContestStarted(std::unique_ptr<ContestMember> contest_member) override; | 
|  | // |GestureRecognizer| | 
|  | void HandleEvent(const fuchsia::ui::input::accessibility::PointerEvent& pointer_event) override; | 
|  | // |GestureRecognizer| | 
|  | std::string DebugName() const override; | 
|  |  | 
|  | private: | 
|  | // Magnification is enabled by a triple 1-finger tap or a double 3-finger tap. | 
|  | // Once it is enabled, zoom can be adjusted by pinching, and the view can be | 
|  | // dragged to pan (with at least two fingers to start, after which a single | 
|  | // finger will do). | 
|  | // | 
|  | // Alternately, magnification can be temporary if the last tap is held down, | 
|  | // in which case panning focuses on the area of the display that would be | 
|  | // under the finger in an unmagnified view. | 
|  | class Trigger { | 
|  | public: | 
|  | // This does not update the primer type, which is only updated on commit. | 
|  | // This should be checked on tap begin and update. | 
|  | bool ShouldTrigger(input::GestureDetector::TapType tap_type) const; | 
|  |  | 
|  | // Tests whether the given tap type could be part of a trigger gesture, to support early defeat | 
|  | // declaration in the gesture arena. | 
|  | bool CanTrigger(input::GestureDetector::TapType tap_type) const; | 
|  |  | 
|  | // Only taps can prime this gesture. When a tap is committed, update the | 
|  | // primer. | 
|  | void OnTapCommit(input::GestureDetector::TapType tap_type); | 
|  |  | 
|  | // Cancels the trigger, on move or final commit. | 
|  | void Reset(); | 
|  |  | 
|  | bool is_primed() const { return primer_type_ != PrimerType::kNotPrimed; } | 
|  |  | 
|  | private: | 
|  | // The most wonderful thing about triggers is I'm not the only one! | 
|  | enum class PrimerType { | 
|  | kNotPrimed, | 
|  | // 2 3-finger taps | 
|  | k2x3, | 
|  | // 3 1-finger taps - first tap | 
|  | k3x1_1, | 
|  | // 3 1-finger taps - second tap | 
|  | // They're bouncy trouncy flouncy pouncy fun fun fun fun fun. | 
|  | k3x1_2 | 
|  | }; | 
|  |  | 
|  | PrimerType primer_type_ = PrimerType::kNotPrimed; | 
|  | }; | 
|  |  | 
|  | class Interaction; | 
|  |  | 
|  | // Represents current and pending state resulting from control gestures (not including animation | 
|  | // progress). We may choose to remove this structure after Magnifier is broken into component | 
|  | // recognizers with their own post-win event streaming. | 
|  | struct ControlState { | 
|  | float transition_rate = 0; | 
|  | float magnified_scale = kDefaultScale; | 
|  | glm::vec2 magnified_translation = {0, 0}; | 
|  |  | 
|  | bool operator==(const ControlState& o) const; | 
|  | bool operator!=(const ControlState& o) const; | 
|  |  | 
|  | // Helper that sets the magnified translation to focus on the given screen coordinate. This does | 
|  | // not call |UpdateTransform|. | 
|  | void FocusOn(const glm::vec2& focus); | 
|  | }; | 
|  |  | 
|  | // |input::GestureDetector::Delegate| | 
|  | std::unique_ptr<input::GestureDetector::Interaction> BeginInteraction( | 
|  | const input::Gesture* gesture) override; | 
|  |  | 
|  | // Resets the gesture detector and trigger, and cancels the tap timeout if scheduled. | 
|  | void ResetRecognizer(); | 
|  |  | 
|  | // Rejects unfulfilled multitap gestures on timeout. The determination of when to post this task | 
|  | // is governed by the |Interaction|, but the timeout itself can outlive the |Interaction| (but not | 
|  | // the |Magnifier|). | 
|  | void ResetTaps(); | 
|  |  | 
|  | // Sends the updated transform to the handler. | 
|  | void UpdateTransform(); | 
|  | // Sends the updated transform if it is the |current_transform_|. | 
|  | void UpdateIfActive(const ControlState* state); | 
|  | void TransitionIntoZoom(ControlState* state); | 
|  | void TransitionOutOfZoom(ControlState* state); | 
|  |  | 
|  | bool is_magnified(const ControlState* state) const; | 
|  |  | 
|  | fuchsia::accessibility::MagnificationHandlerPtr handler_; | 
|  |  | 
|  | input::GestureDetector gesture_detector_; | 
|  | fxl::WeakPtr<Interaction> interaction_; | 
|  |  | 
|  | float transition_progress_ = 0; | 
|  | // Double-buffered state allows us to defer updates from gestures until after we've won. | 
|  | ControlState buffered_state_[2]; | 
|  | // Represents committed control state resulting from winning gestures. This is a pointer to allow | 
|  | // ongoing interactions to route updates independent from when the gesture is awarded a win. | 
|  | ControlState* current_state_ = &buffered_state_[0]; | 
|  | // Represents pending control state accumulated by contending gestures. | 
|  | ControlState* pending_state_ = &buffered_state_[1]; | 
|  | bool update_in_progress_ = false, update_pending_ = false; | 
|  |  | 
|  | Trigger trigger_; | 
|  |  | 
|  | callback::ScopedTaskRunner handler_scope_; | 
|  | // Task that handles timeouts to reject unfulfilled multitap gestures. | 
|  | async::TaskClosureMethod<Magnifier, &Magnifier::ResetTaps> reset_taps_; | 
|  |  | 
|  | // This should be last as destroying it can trigger cleanup actions that depend on other state. | 
|  | std::unique_ptr<ContestMember> contest_member_; | 
|  | }; | 
|  |  | 
|  | }  // namespace a11y | 
|  |  | 
|  | #endif  // SRC_UI_A11Y_LIB_MAGNIFIER_MAGNIFIER_H_ |