-
Notifications
You must be signed in to change notification settings - Fork 4
Navigation & Focus
Navigation and focus solve two different problems in a terminal application.
- Navigation decides which screen is currently displayed.
- Focus decides which component receives keyboard input.
Although they often work together, they are completely independent systems.
RetUI manages screens using a stack.
Each new screen can be pushed onto the stack, replaced, or removed, making navigation similar to a web browser's history.
Navigate to a new screen while keeping the current one in the history.
retui.PushScreen("settings")The user can later return using PopScreen().
Go back to the previous screen.
retui.PopScreen()Replace the current screen without keeping it in history.
retui.ReplaceScreen("login")This is useful after:
- Login
- Logout
- Authentication redirects
- Splash screens
The previous screen is discarded, so pressing Back won't return to it.
Read the active screen.
screen := retui.CurrentScreen()Example:
if retui.CurrentScreen() == "settings" {
// Show settings
}Determine whether a previous screen exists.
retui.CanPopScreen()Useful for deciding whether to display a Back button.
Get the number of screens currently in the navigation stack.
retui.ScreenStackSize()Retrieve the complete navigation stack.
stack := retui.ScreenStackSnapshot()Useful for:
- Breadcrumbs
- Debugging
- Navigation history
Choose the application's first screen.
retui.SetInitialScreen("home")Call this once during application startup.
Clear the entire navigation history.
retui.ResetScreenStack("login")This is commonly used after logout.
func App() retui.Element {
switch retui.CurrentScreen() {
case "home":
return HomeScreen()
case "settings":
return SettingsScreen()
default:
return retui.Text(
"404 - Not Found",
retui.NewStyle().
Foreground(retui.Red),
)
}
}Whenever the current screen changes, RetUI automatically:
- Resets component state
- Rebuilds the UI
- Re-renders the screen
No manual cleanup is required.
Only one interactive component receives keyboard input at a time.
That component is said to have focus.
Move keyboard focus to a component.
retui.SetFocus("name-input")Retrieve the focused component.
retui.CurrentFocus()Determine whether a component currently has focus.
retui.IsFocused("name-input")Remove focus from all components.
retui.Blur()Most built-in components expose a Focused() method.
components.TextInput().
ID("name").
Focused(
retui.IsFocused("name"),
).
Render()Specify the order components should receive focus.
retui.SetFocusOrder([]string{
"name",
"email",
"submit",
})Users can then navigate using Tab and Shift+Tab.
if retui.CurrentKey.Code == retui.KeyTab {
retui.FocusNext()
}
if retui.CurrentKey.Code == retui.KeyShiftTab {
retui.FocusPrev()
}Focus automatically wraps around when reaching the beginning or end of the list.
Dialogs and popups often need temporary focus.
RetUI provides a focus stack for this purpose.
retui.PushFocus("confirm-button")Later:
retui.PopFocus()The previous focus is restored automatically.
Typical use cases include:
- Dialogs
- Confirmation boxes
- Popups
- Floating windows
Sometimes a component must receive every keyboard event.
Dropdowns are a common example.
retui.CaptureFocus("country-picker")Release capture:
retui.ReleaseCaptureFocus()While capture is active:
- Only the captured component receives keyboard events.
- All other focused components are ignored.
Read the captured component.
retui.CapturedFocus()Check whether a component currently owns the capture.
retui.IsCaptured("country-picker")Focus tells a component that it is active.
Hooks allow it to read keyboard events.
For ordinary components.
key, active := retui.UseFocusedKeySimple(
retui.IsFocused("counter"),
)
if active {
switch key.Code {
case retui.KeyUp:
// Handle key
}
}Use this hook for components that may capture focus.
key, active := retui.UseFocusedKey(
"country-picker",
retui.IsFocused("country-picker"),
)
if active {
switch key.Code {
case retui.KeyEnter:
retui.ReleaseCaptureFocus()
}
}This hook automatically respects focus capture, preventing multiple components from handling the same key press.
func Counter() retui.Element {
key, active := retui.UseFocusedKeySimple(
retui.IsFocused("counter"),
)
count, setCount := retui.UseState(0)
if active {
switch key.Code {
case retui.KeyUp:
setCount(count + 1)
case retui.KeyDown:
setCount(count - 1)
}
}
return retui.Text(
fmt.Sprintf("Count: %d", count),
retui.NewStyle(),
)
}| Function | Description |
|---|---|
PushScreen(id) |
Navigate to a new screen |
PopScreen() |
Return to the previous screen |
ReplaceScreen(id) |
Replace the current screen |
CurrentScreen() |
Get the active screen |
CanPopScreen() |
Check whether Back is available |
SetInitialScreen(id) |
Set the application's first screen |
ResetScreenStack(id) |
Clear navigation history |
ScreenStackSize() |
Number of screens in the stack |
ScreenStackSnapshot() |
Retrieve the navigation stack |
| Function | Description |
|---|---|
SetFocus(id) |
Focus a component |
CurrentFocus() |
Get the focused component |
IsFocused(id) |
Check whether a component is focused |
Blur() |
Remove focus |
SetFocusOrder() |
Define Tab navigation |
FocusNext() |
Move to the next component |
FocusPrev() |
Move to the previous component |
PushFocus() |
Temporarily save and change focus |
PopFocus() |
Restore previous focus |
CaptureFocus() |
Capture all keyboard events |
ReleaseCaptureFocus() |
Release captured focus |
CapturedFocus() |
Get captured component |
IsCaptured() |
Check capture ownership |
| Hook | Purpose |
|---|---|
UseFocusedKeySimple() |
Read keyboard input for normal focused components |
UseFocusedKey() |
Read keyboard input while supporting focus capture |
Navigation answers:
"Which screen is currently visible?"
Focus answers:
"Which component receives keyboard input?"
The two systems work together but remain independent. Every RetUI application uses both to create responsive, keyboard-friendly terminal interfaces.