101 lines
3.7 KiB
C++
101 lines
3.7 KiB
C++
/*******************************************************************************
|
|
* FocusManager.h - per-Window logical focus manager.
|
|
******************************************************************************/
|
|
#pragma once
|
|
|
|
#include "CoreTypes.h"
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <vector>
|
|
|
|
class Window;
|
|
|
|
namespace StellarX
|
|
{
|
|
class FocusManager
|
|
{
|
|
struct FocusRequest
|
|
{
|
|
FocusTargetId target{};
|
|
FocusReason reason = FocusReason::Programmatic;
|
|
};
|
|
|
|
struct ScopeEntry
|
|
{
|
|
FocusScopeId id{};
|
|
FocusTargetId root{};
|
|
FocusTargetId initialFocus{};
|
|
FocusTargetId restoreFocus{};
|
|
};
|
|
|
|
::Window* window = nullptr;
|
|
FocusTargetId activeTarget{};
|
|
// Stable identity of the target whose focus callback is currently running.
|
|
// pushScope() uses it because activeTarget is intentionally empty during FocusLost.
|
|
FocusTargetId callbackFocusContext{};
|
|
bool transitionActive = false;
|
|
bool pendingPresent = false;
|
|
FocusRequest pending{};
|
|
FocusError lastError = FocusError::None;
|
|
std::vector<ScopeEntry> scopes;
|
|
std::vector<ControlHandle> invalidatedSubtrees;
|
|
std::uint64_t nextScopeValue = 2;
|
|
|
|
static constexpr std::size_t MaxReentrantRounds = 64;
|
|
|
|
bool querySnapshot(FocusTargetId target, FocusTargetSnapshot& snapshot) const;
|
|
bool isUnderInvalidatedSubtree(FocusTargetId target) const;
|
|
bool isWithinSubtree(FocusTargetId target, FocusTargetId root) const;
|
|
bool isWithinActiveScope(FocusTargetId target) const;
|
|
FocusError validateTarget(FocusTargetId target, FocusReason reason,
|
|
FocusTargetSnapshot* snapshot = nullptr) const;
|
|
FocusError validateCurrentTarget(FocusTargetId target,
|
|
FocusTargetSnapshot* snapshot = nullptr) const;
|
|
FocusPolicy requiredPolicy(FocusReason reason) const noexcept;
|
|
FocusChangeResult performTransition(FocusTargetId target, FocusReason reason);
|
|
FocusTargetId findInvalidationFallback(FocusTargetId invalidTarget) const;
|
|
std::vector<FocusTargetSnapshot> collectOrderedTargets(bool tabStopsOnly) const;
|
|
void queueReentrantRequest(FocusTargetId target, FocusReason reason) noexcept;
|
|
void normalizeInvalidScopes();
|
|
|
|
public:
|
|
explicit FocusManager(::Window& owner);
|
|
~FocusManager() = default;
|
|
|
|
FocusManager(const FocusManager&) = delete;
|
|
FocusManager& operator=(const FocusManager&) = delete;
|
|
|
|
// Normal calls switch immediately. Reentrant calls replace the per-round pending slot.
|
|
FocusChangeResult requestFocus(FocusTargetId target,
|
|
FocusReason reason = FocusReason::Programmatic);
|
|
FocusChangeResult clearFocus(FocusReason reason = FocusReason::Programmatic)
|
|
{
|
|
return requestFocus({}, reason);
|
|
}
|
|
|
|
FocusTargetId focusedTarget() const noexcept { return activeTarget; }
|
|
bool hasFocus(FocusTargetId target) const noexcept
|
|
{
|
|
return static_cast<bool>(target) && activeTarget == target;
|
|
}
|
|
FocusError getLastError() const noexcept { return lastError; }
|
|
FocusScopeId rootScope() const noexcept;
|
|
FocusScopeId activeScope() const noexcept;
|
|
|
|
// Valid Tab stops in the active scope, ordered by tabIndex and stable tree order.
|
|
std::vector<FocusTargetSnapshot> collectTabStops() const;
|
|
|
|
// Stage 2 provides scope mechanics; real Dialog integration remains in stage 4.
|
|
FocusScopeId pushScope(FocusTargetId scopeRoot, FocusTargetId initialFocus = {});
|
|
bool popScope(FocusScopeId scope);
|
|
|
|
// Called when removal is queued, before physical destruction at mutation flush.
|
|
void notifySubtreePendingRemoval(ControlHandle rootHandle);
|
|
void notifySubtreesPendingRemoval(const std::vector<ControlHandle>& rootHandles);
|
|
// Revalidates active focus after visibility, enabled-state, or policy changes.
|
|
void notifySubtreeStateChanged(ControlHandle rootHandle);
|
|
// Clears invalidation markers after the outer mutation flush.
|
|
void onMutationCommitted();
|
|
};
|
|
}
|