Class ModalStack
Not thread-safe: a backend keeps it on its UI thread, where modals are raised and input is dispatched.
Rules:
- The topmost modal's surface is never blocked (see below). Every modal under it is.
- A non-modal window is blocked iff some active modal locks it: a window-modal (non-null parent) locks only its parent (and the parent's owned popups); a toolkit-modal (null parent) locks every non-modal window.
- A modal may name an owner-exception window it never blocks even when its scope would; in-scene (overlay) modals use it, and their host window stays interactive because the overlay blocks its own content. The exception covers what that host owns as well: a dropdown or a menu is the overlay's own content reaching outside the window to draw itself, not a sibling window the modal is meant to lock. Modals still below the top are unaffected; they are frozen before the exception is consulted.
The top modal is answerable by construction. A modal's surface is the window the user has to reach to dismiss it: itself for a native modal, its host for an in-scene overlay. Exempting that surface is not a convenience; without it the stack can deadlock, and did: a native dialog locks its parent window, the app then raises an in-scene dialog on that parent (an unsaved-changes prompt on the way out is the obvious way in), and now the older modal blocks the newer one's only surface while the newer one blocks the older window. Two open dialogs, neither reachable, and the only way out is killing the process.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final recordOne active modal, for a backend's own rule over them. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionactive()booleanisBlocked(Object window, BiPredicate<Object, Object> locks, BiPredicate<Object, Object> ownsTransient) booleanisEmpty()booleanvoidRemoveswindowfrom the stack; nothing happens if it is not there.voidPushes a modal lockingparent.voidPushes a modal lockingparent(null = toolkit-modal) but never lockingownerExceptionnor any window it owns (null = none).booleanremoveOwnedBy(@Nullable Object owner) Removes every modal whose owner-exception isowner.@Nullable ObjecttopModal()
-
Constructor Details
-
ModalStack
public ModalStack()An empty stack: nothing is modal and nothing is blocked.
-
-
Method Details
-
push
Pushes a modal lockingparent.- Parameters:
window- the modal's own window; never nullparent- the window it locks, ornullfor a toolkit-modal that locks every non-modal window- Throws:
IllegalStateException- ifwindowis already modal
-
push
Pushes a modal lockingparent(null = toolkit-modal) but never lockingownerExceptionnor any window it owns (null = none). This is the shape of an in-scene modal, whose host keeps drawing its own dropdowns and menus as separate windows while the overlay is up.- Parameters:
window- the modal's window, or a token standing for an in-scene modalparent- the window it locks, ornullfor a toolkit-modalownerException- the window it never locks, with what that window owns, ornullfor none- Throws:
IllegalStateException- ifwindowis already modal
-
pop
Removeswindowfrom the stack; nothing happens if it is not there.- Parameters:
window- the modal that closed
-
removeOwnedBy
Removes every modal whose owner-exception isowner. It is called when that host window closes, since an in-scene modal has no window of its own to close.- Parameters:
owner- the host window that closed- Returns:
- whether anything was removed
-
active
- Returns:
- the active modals, the oldest first
-
isEmpty
public boolean isEmpty()- Returns:
- whether no modal is active
-
topModal
- Returns:
- the topmost (interactive) modal window, or
nullif none
-
isModal
- Parameters:
window- a window or token- Returns:
- whether
windowis one of the active modals
-
isBlocked
public boolean isBlocked(Object window, BiPredicate<Object, Object> locks, BiPredicate<Object, Object> ownsTransient) - Parameters:
window- the window whose input we are deciding onlocks-locks.test(parent, w)is true iffwisparentor one of its owned popups: the SCOPE a window-modal locks, which covers everything the parent ownsownsTransient-ownsTransient.test(host, w)is true iffwis a popuphostregistered as its own content. Narrower thanlockson purpose: it is the EXEMPTION, and exempting everything an in-scene modal's host owns left a non-modal dialog clickable underneath the modal.Neither predicate is transitive, and that is a decision. A popup of a popup is not owned by the grandparent. Menus keep a whole cascade inside one window, so the shape does not arise;
ModalStackTestasserts it rather than leaving it to be discovered by the first component that spends a window per level.- Returns:
- whether
window's input is currently blocked by a modal
-