Skip to content

Commit dd8b5e7

Browse files
authored
Merge pull request #10 from Tirtstan/experimental
fixes
2 parents ffb2ddb + f90e5ef commit dd8b5e7

5 files changed

Lines changed: 100 additions & 65 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [3.1.3] - 2026-02-12
9+
10+
### Fixed
11+
12+
- **ViewDismissalInputHandler**: Closing the current view is now delayed to the next frame. This avoids `IndexOutOfRangeException` errors in `InputSystemUIInputModule` when `UI/Cancel` is bound to `Esc` and closing a view changes action maps.
13+
- **ActionMapManager**:
14+
- After any view switch, default action maps are checked and applied again. This makes sure `defaultActionMaps` are used when the last non-root view in a menu chain is closed.
15+
- `CheckAndApplyDefaults()` now uses `SentinalManager.AnyNonRootViewsOpen` to decide when defaults should be active. Defaults are applied whenever there are no non-root views open.
16+
817
## [3.1.2] - 2026-02-06
918

1019
### Fixed

README.md

Lines changed: 57 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Sentinal - Unity Menu Navigation & UI Selection System
22

3-
A Unity package for managing hierarchical menu navigation with history tracking, input system integration, and automatic UI element selection. Perfect for game menus, settings panels, and any UI that requires structured navigation flow.
3+
A Unity package for managing menu navigation with history tracking, input system integration, and automatic UI element selection. Perfect for game menus, settings panels, and any UI that requires structured navigation flow.
44

55
> [!NOTE]
66
> This does not replace **UGUI's navigation system.**
@@ -37,22 +37,22 @@ https://github.com/Tirtstan/Sentinal.git
3737

3838
### Core Navigation
3939

40-
- **Menu/View Tracking**: Navigate through multiple menus with automatic history tracking.
41-
- **Priority-Based Focus**: Views are focused based on priority (higher priority first), with recency as tie-breaker.
42-
- **UI Selection**: Auto-selection of UI elements with memory of last selected items.
43-
- **Root Views**: Views that don't get auto-closed and have special permissions around being closed; can still be hidden (`rootView`).
44-
- **Exclusive Views**: Views that close all other views (except root views) when opened.
40+
- **Menu/View Tracking**: Navigate through multiple menus with automatic history tracking.
41+
- **Priority-Based Focus**: Views are focused based on priority (higher priority first), with recency as tie-breaker.
42+
- **UI Selection**: Auto-selection of UI elements with memory of last selected items.
43+
- **Root Views**: Views that don't get auto-closed and have special permissions around being closed; can still be hidden (`rootView`).
44+
- **Exclusive Views**: Views that close all other views (except root views) when opened.
4545

4646
### Input System Integration
4747

48-
- **Action Map Overlays**: Intelligent action map management that tracks enabled/disabled maps per view.
49-
- **Per-View Input Gating**: Enable/disable input for specific views based on focus.
50-
- **Single or Multi-Player**: Support for single PlayerInput or apply to all players simultaneously.
51-
- **Efficient Switching**: Recomputes action maps when switching between menus, not just on close.
52-
- **Configurable Actions**: Customisable input actions for canceling and re-selecting.
53-
- **Default Action Maps**: Configure action maps to apply when no non-root views are open (e.g., gameplay maps when only HUD/main menu is visible).
54-
- **Flexible Action Map Configuration**: Use `ActionMapConfig` to specify both action map names and their enabled/disabled state for when views are enabled or disabled.
55-
- **Dual Event System Support**: Supports both C# Events (`InvokeCSharpEvents`) and Unity Events (`InvokeUnityEvents`) notification behaviors on `PlayerInput`.
48+
- **Action Map Overlays**: Intelligent action map management that tracks enabled/disabled maps per view.
49+
- **Per-View Input Gating**: Enable/disable input for specific views based on focus.
50+
- **Single or Multi-Player**: Support for single PlayerInput or apply to all players simultaneously.
51+
- **Efficient Switching**: Recomputes action maps when switching between menus, not just on close.
52+
- **Configurable Actions**: Customisable input actions for canceling and re-selecting.
53+
- **Default Action Maps**: Configure action maps to apply when no non-root views are open (e.g., gameplay maps when only HUD/main menu is visible).
54+
- **Flexible Action Map Configuration**: Use `ActionMapConfig` to specify both action map names and their enabled/disabled state for when views are enabled or disabled.
55+
- **Dual Event System Support**: Supports both C# Events (`InvokeCSharpEvents`) and Unity Events (`InvokeUnityEvents`) notification behaviors on `PlayerInput`.
5656

5757
## CORE COMPONENTS
5858

@@ -64,18 +64,18 @@ The central manager that handles all view/menu navigation logic and maintains th
6464

6565
**Public API:**
6666

67-
- `CloseCurrentView()` - Close the focused view in the stack.
68-
- `CloseAllViews()` - Close all views (optionally excluding root views).
69-
- `TrySelectCurrentView()` - Attempt to select the focused view's UI element.
70-
- `AnyViewsOpen` - Check if any views are currently open.
71-
- `CurrentView` - Get the currently focused view selector (priority + recency).
72-
- `MostRecentView` - Get the most recently opened view selector.
67+
- `CloseCurrentView()` - Close the focused view in the stack.
68+
- `CloseAllViews()` - Close all views (optionally excluding root views).
69+
- `TrySelectCurrentView()` - Attempt to select the focused view's UI element.
70+
- `AnyViewsOpen` - Check if any views are currently open.
71+
- `CurrentView` - Get the currently focused view selector (priority + recency).
72+
- `MostRecentView` - Get the most recently opened view selector.
7373

7474
**Events:**
7575

76-
- `OnAdd` - Fired when a new view is added to the stack.
77-
- `OnRemove` - Fired when a view is removed from the stack.
78-
- `OnSwitch` - Fired when switching between views.
76+
- `OnAdd` - Fired when a new view is added to the stack.
77+
- `OnRemove` - Fired when a view is removed from the stack.
78+
- `OnSwitch` - Fired when switching between views.
7979

8080
### `ViewSelector` (Menu Component)
8181

@@ -87,31 +87,31 @@ Add this to any GameObject that represents a menu or navigable view. One that wi
8787

8888
##### **View**
8989

90-
- `priority` - Focus priority (higher values get focus first; equal priority uses recency).
91-
- `firstSelected` - The GameObject to auto-select when this view becomes active.
92-
- `rootView` - Root view: does not get auto-closed, has special permissions around being closed; can still be hidden (e.g. main menu, HUD).
93-
- `exclusiveView` - Close all other views (except root views) when this view opens.
94-
- `hideOtherViews` - Temporarily hide other views while this view is open.
95-
- `trackView` - Whether to include this view in the navigation history stack.
90+
- `priority` - Focus priority (higher values get focus first; equal priority uses recency).
91+
- `firstSelected` - The GameObject to auto-select when this view becomes active.
92+
- `rootView` - Root view: does not get auto-closed, has special permissions around being closed; can still be hidden (e.g. main menu, HUD).
93+
- `exclusiveView` - Close all other views (except root views) when this view opens.
94+
- `hideOtherViews` - Temporarily hide other views while this view is open.
95+
- `trackView` - Whether to include this view in the navigation history stack.
9696

9797
##### **Selection**
9898

99-
- `preventSelection` - Prevent automatic selection (useful for input-only views).
100-
- `autoSelectOnEnable` - Automatically select the first element when the view is enabled.
101-
- `rememberLastSelected` - Remember and restore the last selected UI element.
99+
- `preventSelection` - Prevent automatic selection (useful for input-only views).
100+
- `autoSelectOnEnable` - Automatically select the first element when the view is enabled.
101+
- `rememberLastSelected` - Remember and restore the last selected UI element.
102102

103103
##### **Input**
104104

105-
- (Input System) Add `ViewInputSystemHandler` on the same GameObject to gate input and/or apply action maps.
105+
- (Input System) Add `ViewInputSystemHandler` on the same GameObject to gate input and/or apply action maps.
106106

107107
### `ViewDismissalInputHandler` (Input System: Dismiss + Refocus)
108108

109109
<img src="Documentation/Images/ViewDismissal.png" width="500" alt="View Dismissal Input Handler component."/>
110110

111111
Listens for two Input System actions:
112112

113-
- Cancel/Back → `SentinalManager.Instance.CloseCurrentView()`
114-
- Focus → `SentinalManager.Instance.TrySelectCurrentView()`
113+
- Cancel/Back → `SentinalManager.Instance.CloseCurrentView()`
114+
- Focus → `SentinalManager.Instance.TrySelectCurrentView()`
115115

116116
Recommended placement: **same GameObject as `SentinalManager`**.
117117

@@ -121,17 +121,17 @@ Recommended placement: **same GameObject as `SentinalManager`**.
121121

122122
Per-view handler that can:
123123

124-
- Enable/disable “input enabled” state depending on whether the view is the current view (`InputOnlyWhenCurrent`).
125-
- (Optionally) Apply action map changes when enabled or disabled.
124+
- Enable/disable “input enabled” state depending on whether the view is the current view (`InputOnlyWhenCurrent`).
125+
- (Optionally) Apply action map changes when enabled or disabled.
126126

127127
Key fields:
128128

129-
- `inputOnlyWhenCurrent` (default true)
130-
- `viewSelector` (optional, but required if `inputOnlyWhenCurrent` is true)
131-
- `playerInput` / `playerIndex`
132-
- `applyToAllPlayers`
133-
- `onEnabledActionMaps` - Action maps to configure when this handler is **enabled** (view is active). Each entry specifies the action map name and whether to enable or disable it.
134-
- `onDisabledActionMaps` - Action maps to configure when this handler is **disabled** (view is inactive). Each entry specifies the action map name and whether to enable or disable it.
129+
- `inputOnlyWhenCurrent` (default true)
130+
- `viewSelector` (optional, but required if `inputOnlyWhenCurrent` is true)
131+
- `playerInput` / `playerIndex`
132+
- `applyToAllPlayers`
133+
- `onEnabledActionMaps` - Action maps to configure when this handler is **enabled** (view is active). Each entry specifies the action map name and whether to enable or disable it.
134+
- `onDisabledActionMaps` - Action maps to configure when this handler is **disabled** (view is inactive). Each entry specifies the action map name and whether to enable or disable it.
135135

136136
### `ActionMapManager` (Input System: Global Action Map Coordinator)
137137

@@ -141,14 +141,14 @@ Singleton that tracks and manages action map overlays across views using `ViewIn
141141

142142
**Key features:**
143143

144-
- **Default Action Maps**: Configure action maps to apply when no non-root views are open (controlled by `useDefaultActionMaps` toggle).
145-
- **Action Map History**: Tracks the latest action map state per view selector (source); the same selector overwrites its previous entry. Editor shows one row per view with player index, action type (Enable/Disable/Restore), and map names.
146-
- **Automatic Restoration**: Restores action maps to their previous state when views are closed or disabled.
144+
- **Default Action Maps**: Configure action maps to apply when no non-root views are open (controlled by `useDefaultActionMaps` toggle).
145+
- **Action Map History**: Tracks the latest action map state per view selector (source); the same selector overwrites its previous entry. Editor shows one row per view with player index, action type (Enable/Disable/Restore), and map names.
146+
- **Automatic Restoration**: Restores action maps to their previous state when views are closed or disabled.
147147

148148
**Configuration:**
149149

150-
- `useDefaultActionMaps` - If true, applies `defaultActionMaps` when no non-root views are open. If false, uses current in-memory state.
151-
- `defaultActionMaps` - Array of `ActionMapConfig` entries specifying which action maps to enable/disable when only root views are open.
150+
- `useDefaultActionMaps` - If true, applies `defaultActionMaps` when no non-root views are open. If false, uses current in-memory state.
151+
- `defaultActionMaps` - Array of `ActionMapConfig` entries specifying which action maps to enable/disable when only root views are open.
152152

153153
## USAGE EXAMPLES
154154

@@ -219,9 +219,9 @@ private void OnMenuSwitched(ViewSelector from, ViewSelector to)
219219

220220
## REQUIREMENTS
221221

222-
- **Unity 2021.3** or later
223-
- **Input System package** (optional, for input handling features)
224-
- **TextMeshPro** (for sample scenes)
222+
- **Unity 2021.3** or later
223+
- **Input System package** (optional, for input handling features)
224+
- **TextMeshPro** (for sample scenes)
225225

226226
## INPUT SYSTEM SETUP
227227

@@ -231,8 +231,8 @@ Sentinal supports both **C# Events** (`InvokeCSharpEvents`) and **Unity Events**
231231

232232
**Recommended:** Set `PlayerInput.notificationBehavior` to either:
233233

234-
- `InvokeCSharpEvents` - Uses C# events (`onActionTriggered`, `onControlsChanged`, etc.)
235-
- `InvokeUnityEvents` - Uses Unity Events (`actionEvents`, `controlsChangedEvent`, etc.)
234+
- `InvokeCSharpEvents` - Uses C# events (`onActionTriggered`, `onControlsChanged`, etc.)
235+
- `InvokeUnityEvents` - Uses Unity Events (`actionEvents`, `controlsChangedEvent`, etc.)
236236

237237
**Note:** If `PlayerInput.notificationBehavior` is set to `SendMessages` or `BroadcastMessages`, Sentinal will automatically change it to `InvokeCSharpEvents` at runtime and log a warning. Update the setting in the inspector to avoid runtime changes.
238238

@@ -242,10 +242,10 @@ Sentinal supports both **C# Events** (`InvokeCSharpEvents`) and **Unity Events**
242242

243243
The custom editor shows real-time debugging information:
244244

245-
- **SentinalManager**: Shows focused view vs most recent view, view list with priority and input state.
246-
- **ViewSelector**: Shows priority, history index, focus state, input enabled, connected PlayerInput.
247-
- **ViewInputSystemHandler**: Shows input enabled state and action map configuration (Input System).
248-
- **ActionMapManager**: Shows player count, “Action maps per view” (latest state per view selector, sorted by source), and per-player current action map with enabled maps list.
245+
- **SentinalManager**: Shows focused view vs most recent view, view list with priority and input state.
246+
- **ViewSelector**: Shows priority, history index, focus state, input enabled, connected PlayerInput.
247+
- **ViewInputSystemHandler**: Shows input enabled state and action map configuration (Input System).
248+
- **ActionMapManager**: Shows player count, “Action maps per view” (latest state per view selector, sorted by source), and per-player current action map with enabled maps list.
249249

250250
## BEST PRACTICES
251251

Runtime/Input/InputSystem/ActionMapManager.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ private void OnViewSwitch(ViewSelector previousView, ViewSelector newView)
136136

137137
ApplyHandler(newHandler);
138138
}
139+
140+
CheckAndApplyDefaults();
139141
}
140142

141143
private bool TryGetCachedHandler(ViewSelector view, out ViewInputSystemHandler handler)
@@ -213,9 +215,12 @@ private void CheckAndApplyDefaults()
213215
return;
214216
}
215217

216-
// Only consider non-root views that actually have a ViewInputSystemHandler.
217-
// Views without input management should not influence default action maps.
218-
bool shouldApplyDefaults = !AnyNonRootViewsWithInputHandlersOpen();
218+
// Decide whether defaults should be active:
219+
// - If Sentinal reports no non-root views open at all, always apply defaults.
220+
// - Otherwise, only consider non-root views that actually have a ViewInputSystemHandler;
221+
// views without input management should not influence default action maps.
222+
bool shouldApplyDefaults =
223+
!SentinalManager.Instance.AnyNonRootViewsOpen || !AnyNonRootViewsWithInputHandlersOpen();
219224

220225
if (shouldApplyDefaults && !defaultsApplied)
221226
{

Runtime/Input/InputSystem/ViewDismissalInputHandler.cs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#if ENABLE_INPUT_SYSTEM
2+
using System.Collections;
23
using UnityEngine;
34
using UnityEngine.InputSystem;
45

@@ -31,6 +32,7 @@ public class ViewDismissalInputHandler : MonoBehaviour, IPlayerInputHandler
3132
private InputAction cancelInputAction;
3233
private InputAction focusInputAction;
3334
private bool isSubscribed;
35+
private bool closeRequestedThisFrame;
3436

3537
private void Awake()
3638
{
@@ -134,11 +136,30 @@ private void UnsubscribeFromInputActions()
134136
isSubscribed = false;
135137
}
136138

137-
private void OnCancelPerformed(InputAction.CallbackContext context) =>
138-
SentinalManager.Instance.CloseCurrentView();
139+
private void OnCancelPerformed(InputAction.CallbackContext context) => RequestCloseCurrentView();
139140

140-
private void OnCancelCanceled(InputAction.CallbackContext context) =>
141-
SentinalManager.Instance.CloseCurrentView();
141+
private void OnCancelCanceled(InputAction.CallbackContext context) => RequestCloseCurrentView();
142+
143+
private void RequestCloseCurrentView()
144+
{
145+
// Avoid multiple close requests within the same frame.
146+
if (closeRequestedThisFrame)
147+
return;
148+
149+
closeRequestedThisFrame = true;
150+
StartCoroutine(CloseCurrentViewNextFrame());
151+
}
152+
153+
private IEnumerator CloseCurrentViewNextFrame()
154+
{
155+
// Wait until the current input update & UI callbacks have fully finished.
156+
yield return null;
157+
158+
closeRequestedThisFrame = false;
159+
160+
if (SentinalManager.Instance != null)
161+
SentinalManager.Instance.CloseCurrentView();
162+
}
142163

143164
private void OnFocusPerformed(InputAction.CallbackContext context) =>
144165
SentinalManager.Instance.TrySelectCurrentView();

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "com.tirt.sentinal",
3-
"version": "3.1.2",
3+
"version": "3.1.3",
44
"displayName": "Sentinal",
55
"description": "A package that provides a set of tools to manage menu/view navigation and auto-selection in Unity.",
66
"unity": "2021.3",

0 commit comments

Comments
 (0)