blob: edac69d2af1749c3ad68780118a8bcb7789f259a [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_SCENIC_LIB_GFX_ENGINE_VIEW_TREE_H_
#define SRC_UI_SCENIC_LIB_GFX_ENGINE_VIEW_TREE_H_
#include <fuchsia/ui/annotation/cpp/fidl.h>
#include <fuchsia/ui/focus/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <zircon/types.h>
#include <optional>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>
#include "src/ui/lib/escher/geometry/transform.h"
#include "src/ui/scenic/lib/gfx/id.h"
#include "src/ui/scenic/lib/scenic/event_reporter.h"
namespace scenic_impl::gfx {
// Forward declaration to avoid circular include.
class ViewHolder;
// Represent the tree of ViewRefs in a scene graph, and maintain the global "focus chain".
//
// Types. A tree Node is either a RefNode or a AttachNode [1]. RefNode owns a
// fuchsia::ui::views::ViewRef for generating a focus chain. AttachNode represents the RefNode's
// parent in the scene graph. In GFX, these correspond to View and ViewHolder types; in 2D Layer,
// these correspond to Root and Link types.
//
// State. The main state is a map of Koid->Node, and each Node has a parent pointer of type Koid.
// The root of the tree is a RefNode, and its Koid is cached separately. The focus chain is a
// cached vector of Koid.
//
// Topology. Parent/child types alternate between RefNode and AttachNode. The tree root is a
// RefNode. Each child points to its parent, but parents do not know their children. A RefNode may
// have many AttachNode children, but an AttachNode may have only 1 RefNode child. A subtree is
// typically (but not required to be) connected to the global root.
//
// Modifications. Each command processor (such as GFX or 2D Layer) must explicitly arrange node
// creation, node destruction, and node connectivity changes. Modifications directly mutate the
// global tree [2].
//
// Invariants. Tree update operations and focus transfer operations are required to keep the maps,
// root, and focus chain in a valid state, where each parent pointer refers to a valid entry in the
// nodes_ map, the root is a valid entry in the nodes_ map, the focus chain is correctly updated
// [3], and each SessionId/RefNode pair has an entry in the ref_node_koids_ map.
//
// Ownership. The global ViewTree instance is owned by SceneGraph.
//
// Event Dispatch. The tree, on explicit request, performs direct dispatch of necessary events, such
// as for fuchsia::ui::input::FocusEvent. Each node caches a weak pointer to its appropriate
// EventReporter. We assume that the EventReporter interface will grow to accommodate future needs.
//
// Remarks.
// [1] We don't need to explicitly represent the abstract Node type itself.
// [2] We *could* make the tree copyable for double buffering, but at the cost of extra complexity
// and/or performance in managing ViewRef (eventpair) resources.
// [3] If performance is an issue, we could let the focus chain go stale, and repair it explicitly.
class ViewTree {
public:
// Represent a RefNode's parent, such as a ViewHolder in GFX, or a Link in 2D Layer.
// Invariant: Child count must be 0 or 1.
struct AttachNode {
zx_koid_t parent = ZX_KOID_INVALID;
};
// Represent a "view" node of a ViewTree.
// - May have multiple children.
struct RefNode {
zx_koid_t parent = ZX_KOID_INVALID;
fuchsia::ui::views::ViewRef view_ref;
// Focus events are generated and dispatched along this interface.
EventReporterWeakPtr event_reporter;
// Park a callback that returns whether a view may currently receive focus.
fit::function<bool()> may_receive_focus;
// Park a callback that returns the current global transform of the node.
fit::function<std::optional<glm::mat4>()> global_transform;
// Park a function that creates an annotation ViewHolder using given ViewHolderToken.
fit::function<void(fxl::RefPtr<ViewHolder>)> add_annotation_view_holder;
scheduling::SessionId session_id = 0u; // Default value: an invalid ID.
};
// Provide detail on if/why focus change request was denied.
// Specific error-handling policy is responsibility of caller.
enum class FocusChangeStatus {
kAccept = 0,
kErrorRequestorInvalid,
kErrorRequestInvalid,
kErrorRequestorNotAuthorized,
kErrorRequestorNotRequestAncestor,
kErrorRequestCannotReceiveFocus,
kErrorUnhandledCase, // last
};
// Return the current focus chain with cloned ViewRefs.
// - Error conditions should not force the return of an empty focus chain; instead, the root_, if
// valid, should be returned. This allows client-side recovery from focus loss.
fuchsia::ui::focus::FocusChain CloneFocusChain() const;
// Return the current focus chain.
const std::vector<zx_koid_t>& focus_chain() const;
// Return parent's KOID, if valid. Otherwise return std::nullopt.
// Invariant: child exists in nodes_ map.
std::optional<zx_koid_t> ParentOf(zx_koid_t child) const;
// Return the scheduling::SessionId declared for a tracked node.
// Always return 0u for AttachNode, otherwise return stored value for RefNode.
// NOTE: Multiple KOIDs can return the same SessionId
scheduling::SessionId SessionIdOf(zx_koid_t koid) const;
// Return the event reporter declared for a tracked node.
// Be forgiving: If koid is invalid, or is untracked, return a null event reporter.
// Note that a valid and tracked koid may still return null, or later become null.
EventReporterWeakPtr EventReporterOf(zx_koid_t koid) const;
// Return the global transform of the node attached to a tracked |koid|.
// Returns std::nullopt if no node was found or the node had no valid global transform.
std::optional<glm::mat4> GlobalTransformOf(zx_koid_t koid) const;
// A session may have registered one or more RefNodes (typically just one).
// Return the *connected* RefNode KOID associated with a particular SessionId.
// Invariant: at most one RefNode KOID transitively connects to root_.
// - This operation is O(N^2) in the number of RefNodes, but typically is linear in depth of tree.
std::optional<zx_koid_t> ConnectedViewRefKoidOf(SessionId session_id) const;
// Return true if koid is (1) valid and (2) exists in nodes_ map.
bool IsTracked(zx_koid_t koid) const;
// Given a node's KOID, return true if it transitively connects to root_.
// Pre: koid exists in nodes_ map
// Invariant: each parent reference exists in nodes_ map
// - This operation is O(N) in the depth of the view tree.
bool IsConnected(zx_koid_t koid) const;
// "RTTI" for type validity.
bool IsRefNode(zx_koid_t koid) const;
// Return true if koid has "may receive focus" property set to true.
// Pre: koid exists in nodes_ map
// Pre: koid is a valid RefNode
// NOTE: Scene connectivity is not required.
bool MayReceiveFocus(zx_koid_t koid) const;
// Try creating an annotation ViewHolder as the child of the View "koid" refers to.
// Return the creation result enum.
zx_status_t AddAnnotationViewHolder(zx_koid_t koid, fxl::RefPtr<ViewHolder> view_holder) const;
// Debug-only check for state validity. See "Invariants" section in class comment.
// - Runtime is O(N^2), chiefly due to the "AttachNode, when a parent, has one child" check.
bool IsStateValid() const;
// Request focus transfer to the proposed ViewRef's KOID. Return kAccept if successful.
// - If the KOID is not in nodes_ map, or isn't a ViewRef, or isn't connected to the root, then
// return error.
// - If the KOID is otherwise valid, but violates the focus transfer policy, then return error.
FocusChangeStatus RequestFocusChange(zx_koid_t requestor, zx_koid_t request);
// Update tree topology.
// Pre: view_ref is a valid ViewRef
// Pre: view_ref not in nodes_ map
// Pre: may_receive_focus is a non-null callback
// Pre: global_transform is a non-null callback
void NewRefNode(fuchsia::ui::views::ViewRef view_ref, EventReporterWeakPtr reporter,
fit::function<bool()> may_receive_focus,
fit::function<std::optional<glm::mat4>()> global_transform,
fit::function<void(fxl::RefPtr<ViewHolder>)> add_annotation_view_holder,
scheduling::SessionId session_id = 0u);
// Pre: koid is a valid KOID
// Pre: koid not in nodes_ map
void NewAttachNode(zx_koid_t koid);
// Pre: koid exists in nodes_ map
// Post: each parent reference to koid set to ZX_KOID_INVALID
// Post: if root_ is deleted, root_ set to ZX_KOID_INVALID
void DeleteNode(zx_koid_t koid);
// Pre: if valid, koid exists in nodes_map
// Pre: if valid, koid is a valid RefNode
// Pre: if valid, koid has "may receive focus" property
// Post: root_ is set to koid
// NOTE: koid can be ZX_KOID_INVALID, if the intent is to disconnect the entire tree.
void MakeGlobalRoot(zx_koid_t koid);
// Pre: child exists in nodes_ map
// Pre: parent exists in nodes_ map
// Invariant: child type != parent type
void ConnectToParent(zx_koid_t child, zx_koid_t parent);
// Pre child exists in nodes_ map
// Pre: child.parent exists in nodes_ map
// Post: child.parent set to ZX_KOID_INVALID
void DisconnectFromParent(zx_koid_t child);
// Debug aid.
// - Do not rely on the concrete format for testing or any other purpose.
// - Do not rely on this function being runtime efficient; it is not guaranteed to be.
std::string ToString() const;
private:
// Utility.
fuchsia::ui::views::ViewRef CloneViewRefOf(zx_koid_t koid) const;
// Ensure the focus chain is valid; preserve as much of the existing focus chain as possible.
// - If the focus chain is still valid, do nothing.
// - Otherwise, "trim" the focus chain so that every pairwise parent-child relationship is valid
// in the current tree.
// - Runtime is O(N) in the depth of the view tree, even for an already-valid focus chain.
// - Mutator operations must call this function when finishing.
// Post: if root_ is valid, (1) focus_chain_ is a prefix from the previous focus_chain_,
// (2) each element of focus_chain_ is a RefNode's KOID, and (3) each adjacent pair of
// KOIDs (P, R) is part of the ancestor hierarchy (P - Q - R) in the view tree.
// Post: if root_ is invalid, focus_chain_ is empty.
void RepairFocus();
// Map of ViewHolder's or ViewRef's KOID to its node representation.
// - Nodes that are connected have an unbroken parent chain to root_.
// - Nodes may be disconnected from root_ and still inhabit this map.
// - Lifecycle (add/remove/connect/disconnect) is handled by callbacks from command processors.
std::unordered_map<zx_koid_t, std::variant<AttachNode, RefNode>> nodes_;
// The root of this ViewTree: a RefNode.
zx_koid_t root_ = ZX_KOID_INVALID;
// Multimap of Session ID to RefNode's KOID.
// Each RefNode is tied to a particular Session ID. If a query against a particular Session ID
// comes up empty, then that Session has not yet created a RefNode.
// - This map must be kept in sync with the nodes_ map.
// - Invariant: a map key must never be 0u - an invalid Session Id.
// - Invariant: a map value's KOID must refer to a RefNode in the nodes_ map.
// - Invariant: a KOID is the map value for at most one SessionId.
// - Invariant: for each session, at most one RefNode is connected to root_.
// - Lifecycle (add/remove) is handled by callbacks from command processors.
std::unordered_multimap<SessionId, zx_koid_t> ref_node_koids_;
// The focus chain. The last element is the ViewRef considered to "have focus".
// - Mutator operations are required to keep the focus chain updated.
// - If no view has focus (because there is no root), then the focus chain is empty.
std::vector<zx_koid_t> focus_chain_;
};
struct ViewTreeNewRefNode {
fuchsia::ui::views::ViewRef view_ref;
EventReporterWeakPtr event_reporter;
fit::function<bool()> may_receive_focus;
fit::function<std::optional<glm::mat4>()> global_transform;
fit::function<void(fxl::RefPtr<ViewHolder>)> add_annotation_view_holder;
scheduling::SessionId session_id = 0u;
};
struct ViewTreeNewAttachNode {
zx_koid_t koid = ZX_KOID_INVALID;
};
struct ViewTreeDeleteNode {
zx_koid_t koid = ZX_KOID_INVALID;
};
struct ViewTreeMakeGlobalRoot {
zx_koid_t koid = ZX_KOID_INVALID;
};
struct ViewTreeConnectToParent {
zx_koid_t child = ZX_KOID_INVALID;
zx_koid_t parent = ZX_KOID_INVALID;
};
struct ViewTreeDisconnectFromParent {
zx_koid_t koid = ZX_KOID_INVALID;
};
// Handy aliases; suitable for client usage.
using ViewTreeUpdate =
std::variant<ViewTreeNewRefNode, ViewTreeNewAttachNode, ViewTreeDeleteNode,
ViewTreeMakeGlobalRoot, ViewTreeConnectToParent, ViewTreeDisconnectFromParent>;
using ViewTreeUpdates = std::vector<ViewTreeUpdate>;
// Handy ViewRef-specific utility.
zx_koid_t ExtractKoid(const fuchsia::ui::views::ViewRef& view_ref);
} // namespace scenic_impl::gfx
#endif // SRC_UI_SCENIC_LIB_GFX_ENGINE_VIEW_TREE_H_