# Control Focus Switching and TAB/ALT Handling

## Architecture Overview

Control focus management in GacUI operates across three distinct architectural layers, each with specific responsibilities:

### Layer 1: Composition-Level Focus (`GuiGraphicsComposition`)

The lowest layer handles focus at the composition level through `GuiGraphicsHost`:

- `GuiGraphicsHost` maintains the currently focused composition in the `focusedComposition` member variable
- `GuiGraphicsHost::SetFocusInternal` is the core focus-switching method that:
  - Fires `lostFocus` event on the currently focused composition (if any)
  - Updates `focusedComposition` to the new composition
  - Resets caret point to (0,0)
  - Fires `gotFocus` event on the newly focused composition
- `GuiGraphicsHost::SetFocus` validates that the composition belongs to this host before calling `SetFocusInternal`
- `GuiGraphicsHost::ClearFocus` removes focus from any composition
- `GuiGraphicsHost::GetFocusedComposition` returns the currently focused composition
- Each composition has an event receiver that fires `gotFocus` and `lostFocus` events

### Layer 2: Control-Level Focus (`GuiControl`)

The middle layer connects controls to composition-level focus:

- Each `GuiControl` has a `focusableComposition` property (can be null)
- `GuiControl::SetFocusableComposition` attaches `gotFocus` and `lostFocus` event handlers to the composition
- When composition gains focus: `GuiControl::OnGotFocus` is called, which:
  - Sets `isFocused = true`
  - Updates control template state
  - Fires `FocusedChanged` event
- When composition loses focus: `GuiControl::OnLostFocus` is called, which:
  - Sets `isFocused = false`
  - Updates control template state
  - Fires `FocusedChanged` event
- `GuiControl::GetFocused` checks if the control is currently focused
- `GuiControl::SetFocused` is the public API to focus a control:
  - Returns early if no focusable composition exists
  - Returns early if control is not visually enabled
  - Returns early if focusable composition is not eventually visible
  - Gets the graphics host from the focusable composition
  - Calls `host->SetFocus(focusableComposition)`

### Layer 3: Focus Loss on State Changes

Focus is automatically cleared when controls undergo certain state changes:

- **When disabled**: `GuiControl::SetEnabled(false)` calls `UpdateVisuallyEnabled`, which triggers `OnLostFocus` if the control was focused
- **When hidden**: `GuiControl::SetVisible(false)` does NOT automatically clear focus (based on test case comments indicating unclear behavior)
- **When deleted**: `GuiGraphicsHost::DisconnectCompositionInternal` is called recursively, clearing `focusedComposition` if it matches the disconnected composition

## TAB Key Navigation

### Architecture Components

TAB navigation is managed by `GuiTabActionManager`, which is created and owned by `GuiGraphicsHost`.

#### IGuiTabAction Interface

Controls implement `IGuiTabAction` service to participate in TAB navigation:

- `GetAcceptTabInput()`: Returns whether control accepts TAB character as input (if true, TAB key is passed to control instead of used for navigation)
- `GetTabPriority()`: Returns priority for TAB order (-1 = lowest priority, 0, 1, 2... = higher priorities)
- `IsTabEnabled()`: Returns whether control can be tabbed to (checks if control is visible and enabled)
- `IsTabAvailable()`: Returns whether control is available for TAB navigation (checks if focusable composition exists)

#### Control List Building (`BuildControlList`)

The TAB manager builds a prioritized list of focusable controls:

- Uses `tab_focus::CollectControls` to recursively collect all controls from the control host
- Groups controls by their `TabPriority` value
- Inserts controls in priority order: 0, 1, 2, ..., then -1 (default/unprioritized)
- For each control, recursively collects its children and inserts them after the parent
- Result is a flattened list `controlsInOrder` sorted by TAB priority
- Cache is invalidated when a control's `TabPriority` changes via `GuiControl::SetTabPriority`

#### Focus Navigation (`GetNextFocusControl`)

Finding the next focusable control:

- Rebuilds control list if cache is invalidated (`available == false`)
- Takes current focused control and offset (+1 for forward TAB, -1 for backward SHIFT+TAB)
- Finds start index in the control list
- Wraps around using modulo arithmetic: `(index + offset + count) % count`
- Loops through controls until finding one that is both `IsTabAvailable()` and `IsTabEnabled()`
- Returns the next focusable control or null if none found

#### Key Event Handling

TAB key processing in `GuiTabActionManager::KeyDown`:

- Only processes TAB key when CTRL and ALT are NOT pressed
- If focused composition's control has `GetAcceptTabInput() == true`, TAB is NOT intercepted (returns false to let control handle it)
- Otherwise, calls `GetNextFocusControl` with offset based on SHIFT key state
- If next control found: calls `next->SetFocused()` and sets `supressTabOnce = true` to prevent TAB character insertion
- Returns true to indicate key was handled

Character suppression in `GuiTabActionManager::Char`:

- Suppresses the TAB character (`\t`) if `supressTabOnce` is true
- Resets `supressTabOnce` to false after checking

## ALT Key Navigation

### Architecture Components

ALT navigation is managed by `GuiAltActionManager`, which is created and owned by `GuiGraphicsHost`.

#### IGuiAltAction Interface

Controls implement `IGuiAltAction` service to have ALT shortcuts:

- `GetAlt()`: Returns the ALT key string (e.g., "F" for ALT+F)
- `IsAltEnabled()`: Returns whether ALT action is enabled (checks visible and enabled state)
- `IsAltAvailable()`: Returns whether ALT action is available (checks if focusable composition and alt string exist)
- `GetAltComposition()`: Returns the composition to show the ALT key label on
- `GetActivatingAltHost()`: Returns the alt host to enter when this action is activated (null = just activate)
- `OnActiveAlt()`: Called when ALT key is activated (default implementation calls `SetFocused()`)
- `IGuiAltAction::IsLegalAlt`: Validates that ALT string contains only uppercase letters (A-Z) and digits (0-9)

#### IGuiAltActionContainer Interface

Controls can implement this to provide multiple ALT actions:

- `GetAltActionCount()`: Returns the number of ALT actions
- `GetAltAction(index)`: Returns the ALT action at the specified index

#### IGuiAltActionHost Interface

Hosts create a hierarchy of ALT contexts that can be entered/exited:

- `GetAltComposition()`: Returns the composition for this host
- `GetPreviousAltHost()`: Returns the parent host in the hierarchy
- `OnActivatedAltHost(previousHost)` and `OnDeactivatedAltHost()`: Called when entering/leaving
- `CollectAltActions(actions)`: Collects all ALT actions for this host

ALT action collection from controls (`IGuiAltActionHost::CollectAltActionsFromControl`):

- Recursively traverses control tree starting from the specified control
- If a control has `IGuiAltActionContainer`: collects all actions from it and executes `continue`
- Else if a control has `IGuiAltAction` and both `IsAltAvailable()` and `IsAltEnabled()` return true: adds the single action and executes `continue`
- **Critical behavior**: These `continue` paths prevent the control's children from being added to the traversal queue
- This creates a "barrier" effect where children are hidden unless you enter a nested ALT context
- A disabled or unavailable single `IGuiAltAction` does not create a barrier; collection continues into that control's children

### ALT Mode Lifecycle

#### Activation

Pressing ALT (KEY_MENU) without CTRL or SHIFT:

- `GuiAltActionManager::KeyDown` checks if control host implements `IGuiAltActionHost`
- Calls `EnterAltHost` to enter ALT mode

`EnterAltHost` process:

- Calls `ClearAltHost` to clean up previous state
- Calls `host->CollectAltActions(actions)` to get all available actions
- If no actions available: calls `CloseAltHost` and exits
- Calls `host->OnActivatedAltHost(currentAltHost)` with previous host
- Updates `currentAltHost` to the new host
- Calls `CreateAltTitles` to create visual labels

#### Visual Label Creation (`CreateAltTitles`)

Creating ALT key labels:

- For each ALT action key (grouped by alt string):
  - If single action and non-empty key: uses key as-is
  - If multiple actions with same key: appends numeric suffix (0-999) with zero-padding
  - Creates `currentActiveAltActions` dictionary mapping full key (e.g., "F", "F00", "F01") to action
- For each action in `currentActiveAltActions`:
  - Creates a `GuiLabel` with `ThemeName::ShortcutKey` theme
  - Sets label text to the key string
  - Adds label to the action's composition
  - Stores label in `currentActiveAltTitles` dictionary
- Calls `FilterTitles` to update visibility

#### Key Input Handling (`EnterAltKey`)

Processing typed characters in ALT mode:

- Appends typed character to `currentAltPrefix`
- Checks if `currentAltPrefix` matches any action key in `currentActiveAltActions`
- If no match: calls `FilterTitles` and removes last character if no visible titles remain
- If exact match found:
  - If action has `GetActivatingAltHost()`: calls `EnterAltHost` to enter nested ALT host
  - Otherwise: calls `CloseAltHost` to exit ALT mode
  - Calls `action->OnActiveAlt()` to activate the action (usually focuses the control)
  - `EnterAltKey` returns true; `GuiAltActionManager::KeyDown` then stores the source key in `supressAltKey` to prevent it from being processed further

#### Label Filtering (`FilterTitles`)

Updating label visibility based on current prefix:

- Iterates through all labels in `currentActiveAltTitles`
- If label's key starts with `currentAltPrefix`: shows label and formats text with brackets around next character (e.g., "F[0]0")
- Otherwise: hides label
- Returns count of visible labels

#### Exiting ALT Mode

Ways to exit ALT mode:

- Press ESCAPE: calls `LeaveAltHost` to exit current host and restore previous
- Press BACKSPACE: calls `LeaveAltKey` to remove last character from prefix
- A mouse-button down or double-click makes `GuiGraphicsHost` call `GuiAltActionManager::CloseAltHost()`
- An unmatched keyboard key is consumed while ALT mode remains active; an invalid prefix character is removed when it leaves no visible titles
- `CloseAltHost`: clears all state, deletes all labels, and exits all hosts in the hierarchy

#### Character and Key Suppression

Input suppression while in ALT mode:

- `GuiAltActionManager::Char` returns true to suppress all character input while in ALT mode or if `supressAltKey` is set
- `GuiAltActionManager::KeyUp` suppresses the key-up event for the key stored in `supressAltKey`

### Nested ALT Hosts

#### Why Nested ALT Hosts Are Needed

The `continue` statements in `CollectAltActionsFromControl` create a "barrier" for an `IGuiAltActionContainer`, or for a single `IGuiAltAction` that is both available and enabled. This prevents the control's children from being collected at the parent level. Nested ALT hosts provide a mechanism to enter the control's context and collect children's ALT actions at a nested level.

**Design Rationales for Custom `GetActivatingAltHost` Implementations:**

1. **Non-Child Control Relationships** (`GuiMenuButton`):
   - Problem: A menu button's submenu is a separate popup window, NOT a child control
   - Default collection only traverses child controls, cannot reach submenu items
   - Solution: Returns `subMenu->QueryTypedService<IGuiAltActionHost>()` to switch context to submenu window
   - Result: Pressing ALT+[key] on menu button enters submenu's ALT context

2. **Intentional Blocking** (`GuiComboBoxBase`):
   - Problem: UX design requires ALT key to just open dropdown, NOT enter nested navigation
   - Default would collect ALT actions from dropdown's controls
   - Solution: Explicitly returns `GuiSelectableButton::GetActivatingAltHost()` (usually null)
   - Result: ALT key only opens dropdown without entering nested mode

3. **Dynamic/Temporary Content** (`GuiVirtualDataGrid`):
   - Problem: Cell editor is created on-demand, not a permanent child
   - Default collection only sees permanent children, misses temporary editor
   - Solution: When `currentEditor` has a focus control whose `IGuiAltAction` is both available and enabled, calls `SetAltComposition(currentEditor->GetTemplate())` and `SetAltControl(focusControl, true)`, then returns `this`; otherwise clears both values and delegates to `GuiVirtualListView::GetActivatingAltHost()`
   - Result: Can navigate within cell editor using ALT keys

4. **Scoped Navigation for Dense Control Groups** (`GuiRibbonGroup`):
   - Problem: Ribbon groups contain many buttons, exposing all at window level creates too many conflicts
   - Default would collect all buttons at same level as the group (if group didn't have its own ALT action)
   - But: when `GuiRibbonGroup` has an available and enabled ALT action, it blocks children via `continue`
   - Solution: Returns `this` when `IsAltAvailable()` is true, creating two-level navigation
   - Result: First press ALT+[group-key] to enter group, then press ALT+[button-key] to select button
   - Benefit: Reduces conflicts and creates logical grouping

5. **Optional Scoped Navigation** (`GuiDatePicker`):
   - Problem: Date picker displays calendar with many clickable date cells, needs optional nested navigation
   - Solution: Returns `this` when `nestedAlt` constructor parameter is true
   - Result: When enabled, pressing ALT+[key] enters calendar's ALT context for date selection

#### The Critical Role of the `continue` Barrier

The `continue` statement in `CollectAltActionsFromControl` serves as a crucial design element:

- **Without nested hosts**: An ALT container, or an available and enabled single ALT action, prevents children from being collected at that level
- **With nested hosts**: `GetActivatingAltHost` provides a way to "un-hide" children by entering a nested context that re-collects them
- **For containers like `GuiRibbonGroup`**: This creates hierarchical navigation instead of flat navigation, reducing ALT key conflicts and improving scalability for interfaces with many controls

## Event Flow Integration

### Key Event Processing Chain

`GuiGraphicsHost::KeyDown` processes native window key events in this order:

1. First tries `GuiAltActionManager::KeyDown`: if ALT manager is active or activates, returns true to consume event
2. Then tries `GuiTabActionManager::KeyDown`: if TAB key is handled, returns true to consume event
3. Then tries shortcut key manager (if exists)
4. Finally delivers to focused composition's event receiver if not consumed

### Character Event Processing Chain

`GuiGraphicsHost::Char` processes character input in this order:

1. First tries `GuiAltActionManager::Char`: suppresses all input while in ALT mode
2. Then tries `GuiTabActionManager::Char`: suppresses TAB character if just navigated
3. Finally delivers to focused composition's event receiver if not suppressed

### Control Visibility and Enable State

`GuiControl::IsControlVisibleAndEnabled` determines if a control can receive focus:

- Traverses up parent chain checking both `GetVisible()` and `GetEnabled()`
- Used by both `IsAltEnabled()` and `IsTabEnabled()` to determine if control can receive focus via ALT or TAB
- Ensures that if a parent is disabled or invisible, all children are excluded from ALT/TAB navigation

## Test Coverage

### TAB Navigation Tests

`TestApplication_Tab.cpp` contains comprehensive TAB navigation tests:

- `TabNavigate`: Tests forward TAB through 3 buttons and backward SHIFT+TAB
- `TabNavigateWithContainer`: Tests TAB through 5 buttons including nested containers (GroupBox)
- `FocusedAndDisable`: Tests that disabling focused control clears focus
- `FocusedAndHide`: Tests hiding focused control (focus behavior noted as unclear in comment)
- `FocusedAndDelete`: Tests deleting focused control clears focus

### ALT Focus Test

`TestControls_CoreApplication_GuiControl.cpp` contains `AltFocus` test:

- Tests ALT key activation in nested control scenarios
