-
Notifications
You must be signed in to change notification settings - Fork 4
Window System
A lightweight, thread-safe window manager for retui terminal applications. It adds floating windows, modal dialogs, Z-ordering, and focus management on top of retui's component model — without retui needing to know anything about windows directly.
A Window is a floating UI surface — think of it as a resizable, movable box that sits on top of your app's main screen. It has a title, a position, a size, and content to render. A window can be:
- Non-modal — floats above the background but doesn't block interaction with it (like a status panel or a docked palette).
- Modal — blocks all interaction with everything behind it until it's closed (like a confirmation dialog or an edit form).
Windows start out hidden. Nothing shows up on screen until you call .Show().
Every window is tracked by a single global WindowManager. You never create one yourself — the package manages a globalManager instance for you, and most operations are exposed as both:
- Methods on
*WindowManager(if you fetch it viawindow.GetManager()), and - Free package-level functions that operate on the global instance (e.g.
window.CloseAll(),window.GetFocused()).
The manager is responsible for:
- Keeping a registry of all windows (
map[string]*Window) - Maintaining Z-order — which window is drawn on top
- Maintaining a separate modal stack — which modal is currently active
- Tracking focus — which window receives keyboard input
- Triggering re-renders when window state changes
| Non-Modal | Modal | |
|---|---|---|
| Blocks background interaction | ❌ No | ✅ Yes |
| Appears in modalStack | ❌ No | ✅ Yes |
| Escape has default "close" behavior | ❌ No (you wire it yourself) | ✅ Yes (built-in fallback) |
| Tab cycles between it and other modals | N/A | ✅ Yes, if multiple modals stacked |
| Typical use case | Status bar, docked panel, tooltip | Edit form, confirmation dialog, alert |
-
Always return
true/falsedeliberately fromOnKeyPress. Returningtruewhen you didn't actually handle the key will swallow it silently for everyone else; returningfalsewhen you did handle it can let the key "leak" into the background screen. -
Close()is final. Once closed, a window is unregistered and cannot be reshown. If you need a reusable dialog, keep it around and useHide()/Show()instead ofClose()/NewWindow(). - Only one modal blocks input at a time — the topmost one. Lower modals in the stack are inert until the ones above them are closed.
- Escape has default behavior; Tab does not (much). Tab only auto-cycles focus among open modals — it does nothing special for non-modal windows or when nothing is open.
-
Don't forget
SetScreenSize. If you never call it,Center()falls back toDefaultScreenWidth/DefaultScreenHeight(140×40), which may not match your actual terminal. -
All state is protected by mutexes, so it's safe to call window/manager methods from goroutines (e.g. background workers closing a progress dialog when a task finishes). Rendering re-triggers are dispatched via
go wm.triggerRender(), so they won't block the caller.
Q: I pressed Escape and it closed my modal and my whole screen. What happened?
A: Almost certainly your OnKeyPress handler for Escape did something (like calling Close()) but returned false instead of true, so the key kept propagating past the window layer into your background screen's own key handling. Return true whenever you act on a key.
Q: How do I make a window that can't be closed by Escape?
A: Add an OnKeyPress handler that intercepts KeyEscape and returns true without calling Close():
w.OnKeyPress(func(key retui.Key) bool {
if key.Code == retui.KeyEscape {
return true // swallow it — do nothing
}
return false
})
Q: Can I have two modals open with neither blocking the other? A: No — modal means "blocks everything behind it," including other modals lower in the stack. If you need two independent floating panels, use non-modal windows instead.
Q: How do I know which window currently has keyboard focus?
A: window.GetFocused() returns its ID; pass that to window.GetWindowByID(id) to get the *Window.