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