blob: 414f46395538e22d297c779a064b448df03664c0 [file] [log] [blame]
// 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_