Class Dialog
CompletionStage with
the chosen button's result: never a nested event loop, so show*
returns immediately and the stage completes (on the UI thread) later.
Two display modes (setDisplayMode(limn.components.DisplayMode)):
DisplayMode.NATIVE_WINDOW(default): a separate floating native window centered over the owner, with aWindowStyleand optional always-on-top. Modality is enforced by the backend (locked windows dim and ignore input). Not available to a modal dialog over a fullscreen owner; see below.DisplayMode.IN_SCENE: an internal overlay drawn inside the owner window: a scrim that dims/blocks the content behind plus a centered card that fades in. No extra OS window; modality is scoped to that scene.
Three modality scopes (native window mode):
show(Scene)is window-modal: locks only the owner window (and its owned popups); other windows stay interactive.showToolkitModal(Scene)is toolkit-modal: locks every window in the application.showNonModal(Scene)is non-modal: locks nothing; a floating panel alongside a fully interactive owner.
IN_SCENE mode the overlay always captures its own scene's input;
the owner stays interactive (it hosts the overlay) but its sibling
windows are locked like a native modal: show locks the owner's owned
popups, showToolkitModal locks every window. showNonModal is
unsupported in-scene (use a native window for a non-modal dialog).
Over a fullscreen owner a modal dialog is drawn in scene, whatever was asked, for the
reasons DisplayMode gives: an owner fullscreen at a named mode is minimized as soon as
the dialog's window takes the focus, which it does on opening; one at the mode the display is in
already covers the screen, so the card loses nothing drawn inside it; and on a Mac without a GPU
the colour picker's dialog held the UI for 1.4 to 3.7 seconds as a window, against 0.6 to 0.7 in
scene. Its scope is kept: showToolkitModal still locks every other window. A
non-modal dialog is the exception, and stays a window even there: an overlay captures its
window's input, and a non-modal dialog exists so that the owner stays usable, which leaves it no
in-scene form to fall back to. A window that cannot be positioned (Wayland) changes nothing: a
dialog has no anchor to line up with, so the compositor placing it is where it belongs.
displayMode() answers what was chosen.
The same holds for a modal dialog already open as a window when its owner goes fullscreen: it moves into the scene then, card, contents, focus and scope, and is still waiting for its answer; the owner going fullscreen answers nothing. It is left a window only while another dialog or a modal menu is open over it, since moving it would close the window that one belongs to, and nothing moves back into a window when the owner leaves fullscreen.
Stacking
Dialogs stack in either mode and in any combination: the newest is the one the user answers, everything under it is frozen until it closes, and answering the top one hands control back to the one below.One exception, and it changes the display mode: an IN_SCENE dialog raised
over a window that a modal has already locked is presented as a native window
instead, with a warning naming the dialog. An overlay is drawn inside its host window,
so it can only come forward by bringing that window forward, which would hide the
dialog already floating over it. keepInScene() declines the promotion, and a
fullscreen owner never makes it, since a window there does not stack over anything.
Asking before it is cancelled
A dialog the user closes with nothing chosen, by Escape, a click on the scrim, a button whose result is the cancel result, the system closing its own window or a screen reader's dismiss, asks itsonCancel handler first when it has one. The handler
answers now or later, so a form with unsaved changes can ask "Save changes?" over itself and close
only once that is answered. Every other answer closes it at once, and so does dismiss(java.lang.String).
Size
A dialog is a container of sized components rather than a sized component: it has no size knob of its own, it is aControlSize scope that its content
inherits. Its three gutters and its dialogMaxWidth measure cap all follow the
resolved step.
Because every show* takes a Scene, the step is inherited from the
owner scene's root, not from the control that opened the dialog, so a
dialog raised from a SMALL toolbar inside a MEDIUM scene renders MEDIUM. Declare it
with setControlSize(limn.scene.ControlSize), or pass the opening widget to show(Widget), to
get anything else.
A dialog is also bounded, and never grows past what it can be seen in: a native window is capped at the work area of the display it opens on (the monitor less the OS chrome), and an in-scene overlay at the window hosting it, both inset by one gutter so a capped card reads as capped rather than as clipped. Content that does not fit scrolls, and the button row is deliberately outside the scrolling part, so the way out of a dialog can never be scrolled away. A dialog that fits is unaffected: it is exactly as tall as it asks to be, and has nothing to scroll.
A Dialog is single-use: it presents once and resolves once.
Calling any show* method a second time (including after the dialog
closed) throws IllegalStateException; build a new instance per
presentation.
new Dialog("Delete?", "This action cannot be undone.")
.addPrimaryButton("Keep", "cancel")
.addDestructiveButton("Delete", "delete")
.show(scene)
.thenAccept(result -> { if ("delete".equals(result)) ...; });
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic interfaceOne question to a dialog'sonCancelhandler: may the dialog close with its cancel result? Answered once, withDialog.CancelRequest.allow()orDialog.CancelRequest.refuse(), now or later: a handler that shows a dialog of its own over this one keeps the request and answers from that dialog's result. -
Constructor Summary
ConstructorsConstructorDescriptionA dialog with fixed text; see theI18nStringconstructor for localized text.Dialog(I18nString title, I18nString message) A dialog whose title and message follow the UI language; seeI18nString. -
Method Summary
Modifier and TypeMethodDescriptionAdds a secondary (outlined) button that resolves the dialog withresultValue.addButton(I18nString text, @Nullable String resultValue) A secondary button whose caption follows the UI language.addDestructiveButton(String text, @Nullable String resultValue) Adds a button that destroys something ("Delete", "Discard") and resolves the dialog withresultValue: a secondary button with its caption in the palette's danger colour.addDestructiveButton(I18nString text, @Nullable String resultValue) A destructive button whose caption follows the UI language; seeaddDestructiveButton(String, String).addPrimaryButton(String text, @Nullable String resultValue) Adds a primary (filled) button that resolves the dialog withresultValue.addPrimaryButton(I18nString text, @Nullable String resultValue) A primary button whose caption follows the UI language.voidProgrammatically closes the dialog, resolving withresult.How this dialog is actually presented: the requested mode untilshow(limn.scene.Scene)decides otherwise, and the effective one afterwards, which changes once more, toDisplayMode.IN_SCENE, if a modal dialog's owner goes fullscreen while it is a window.floatInsists onDisplayMode.IN_SCENEeven when a modal window is already open over the host, the case this dialog would otherwise be promoted out of, silently and with a warning.@Nullable NativeWindowonCancel(@Nullable Consumer<Dialog.CancelRequest> handler) Asks the application whether the dialog may close when the user closes it with nothing chosen, which is every way of giving the cancel result: Escape, a click on the scrim of a dialog that dismisses on it, a button whose result is the cancel result (judged when it is pressed, Return on the default button included), the system closing the dialog's own window, and an assistive technology's dismiss.CompletionStage<@Nullable String> result()setAlwaysOnTop(boolean value) Whether the native dialog window floats above other windows (defaulttrue).setCancelResult(@Nullable String value) Sets the result delivered on ESC / scrim dismiss (defaultnull): the answer that means nothing was chosen.setContent(@Nullable Widget<?> widget) Puts a widget between the message and the buttons: a form, a preview, a picker.setControlSize(@Nullable ControlSize size) Declares the size step for the whole card, overriding the inherited one: the only size knob a dialog has, and a forwarder rather than an override because aDialogis not aWidget: it lands on the card's root, which is what every control inside the dialog then inherits, in both display modes.setDismissOnScrim(boolean value) Whether clicking the scrim dismisses the dialog (defaultfalse).setDisplayMode(DisplayMode mode) Selects native-window vs.setStyle(WindowStyle newStyle) Sets the native window framing (defaultWindowStyle.UNDECORATED_TRANSLUCENT):DECORATEDuses the OS title bar/border (opaque);UNDECORATED_OPAQUEis a borderless solid card; the default is a borderless glassy panel with rounded, see-through corners.CompletionStage<@Nullable String> Shows the dialog window-modal overowner(locks only its window), or, inDisplayMode.IN_SCENEand over a fullscreen owner, a scene-modal internal overlay.CompletionStage<@Nullable String> show(Scene)overowner's scene, but inheriting the size step fromowneritself rather than from the scene root, the overload to reach for when a SMALL toolbar (or a LARGE settings pane) raises the dialog and the card should match the surface it came from.CompletionStage<@Nullable String> showNonModal(Scene owner) Shows the dialog non-modal overowner: a floating window that locks nothing, so the owner (and every other window) stays fully interactive.CompletionStage<@Nullable String> showNonModal(Widget<?> owner) showNonModal(Scene), inheriting the size step fromowner, which stays the dialog's host until it closes, asshow(Widget)says.CompletionStage<@Nullable String> showToolkitModal(Scene owner) Shows the dialog toolkit-modal overowner(locks every window).CompletionStage<@Nullable String> showToolkitModal(Widget<?> owner) showToolkitModal(Scene), inheriting the size step fromowner, which stays the dialog's host until it closes, asshow(Widget)says.
-
Constructor Details
-
Dialog
A dialog with fixed text; see theI18nStringconstructor for localized text.- Parameters:
title- the title, drawn on the card and read as the dialog's name; never nullmessage- the sentence under the title; empty for none, never null
-
Dialog
A dialog whose title and message follow the UI language; seeI18nString.- Parameters:
title- the title, drawn on the card and read as the dialog's name; never nullmessage- the sentence under the title; empty for none, never null
-
-
Method Details
-
setContent
Puts a widget between the message and the buttons: a form, a preview, a picker. A dialog is the toolkit's one modal surface, and until now the only thing it could say was a sentence; anything that needs an answer richer than a button had to build its own overlay and re-solve focus, dismissal and the scrim.The widget inherits the card's
ControlSizelike everything else in it. Passingnullremoves the current one. UI thread only.- Parameters:
widget- the content, ornullto remove it- Returns:
- this dialog
-
addButton
Adds a secondary (outlined) button that resolves the dialog withresultValue.- Parameters:
text- the caption; never nullresultValue- what the dialog resolves with when this button is pressed; may be null- Returns:
- this dialog
-
addButton
A secondary button whose caption follows the UI language.- Parameters:
text- the caption; never nullresultValue- what the dialog resolves with when this button is pressed; may be null- Returns:
- this dialog
-
addPrimaryButton
Adds a primary (filled) button that resolves the dialog withresultValue.The first one added is also the card's default button: Return resolves the dialog with its result whenever the focused widget did not want the key itself, so Return in a text field answers the dialog, while Return on a focused button presses that button and Return in a
TextAreastill inserts a newline.- Parameters:
text- the caption; never nullresultValue- what the dialog resolves with when this button is pressed; may be null- Returns:
- this dialog
-
addPrimaryButton
A primary button whose caption follows the UI language.- Parameters:
text- the caption; never nullresultValue- what the dialog resolves with when this button is pressed; may be null- Returns:
- this dialog
-
addDestructiveButton
Adds a button that destroys something ("Delete", "Discard") and resolves the dialog withresultValue: a secondary button with its caption in the palette's danger colour.It is never the default button, whatever was added before it, and Return does not press it, even while it holds the focus: so a person typing into the dialog and pressing Return out of habit does not delete. Return then gives the default button's answer, or none in a dialog without one. Nor does it take the dialog's first focus, which goes to the widget the content asked for as it joined (
Scene.requestFocus(limn.scene.Widget<?>)from itsonAttached), else to the first control of the content that takes the keyboard (a field, say), or, when the content has none, to the default button, or else to the first button that destroys nothing; only in a dialog whose every button is destructive does the first of them take it, Return still not pressing it and Escape still cancelling. Space, a click and an assistive technology's press do press it. Give the dialog a primary button for the safe answer, which Return then gives.- Parameters:
text- the caption; never nullresultValue- what the dialog resolves with when this button is pressed; may be null- Returns:
- this dialog
-
addDestructiveButton
A destructive button whose caption follows the UI language; seeaddDestructiveButton(String, String).- Parameters:
text- the caption; never nullresultValue- what the dialog resolves with when this button is pressed; may be null- Returns:
- this dialog
-
setCancelResult
Sets the result delivered on ESC / scrim dismiss (defaultnull): the answer that means nothing was chosen. A button whose result is this one cancels too, and every way of cancelling asksonCancelfirst when it is set.- Parameters:
value- what Escape or a dismissing scrim click resolves with; may be null- Returns:
- this dialog
-
onCancel
Asks the application whether the dialog may close when the user closes it with nothing chosen, which is every way of giving the cancel result: Escape, a click on the scrim of a dialog that dismisses on it, a button whose result is the cancel result (judged when it is pressed, Return on the default button included), the system closing the dialog's own window, and an assistive technology's dismiss. The handler is given aDialog.CancelRequestit answers now or later; without a handler the dialog closes. An answer other than the cancel result, the Save of a question asked over a form, is given with the request's dialog'sdismiss(java.lang.String), which settles the request. Its other buttons, a destructive one included, are answers and never asked about, and neither isdismiss(java.lang.String), the application's own close, nor the dialog's window or its owner going away, nor the application ending.The handler is called on the UI thread, during the user's gesture, before anything fades. While its request is unanswered the dialog asks nothing new: a second Escape does nothing. A handler that throws has refused: the dialog stays open, and the exception reaches the crash handler as any exception from application code on the UI thread does.
- Parameters:
handler- the handler, ornullto clear the slot- Returns:
- this dialog
- Throws:
IllegalStateException- if a handler is already registered
-
setControlSize
Declares the size step for the whole card, overriding the inherited one: the only size knob a dialog has, and a forwarder rather than an override because aDialogis not aWidget: it lands on the card's root, which is what every control inside the dialog then inherits, in both display modes.nullrestores inheritance from the owner scene. UI thread only.- Parameters:
size- the step, ornullto inherit it from the owner- Returns:
- this dialog
-
controlSize
- Returns:
- the step the card currently resolves to (declared, inherited or default)
-
setDisplayMode
Selects native-window vs. internal (in-scene) presentation (defaultDisplayMode.NATIVE_WINDOW).A preference, not a guarantee, in both directions. An
IN_SCENEdialog raised while a modal is already open over its window is presented as a native one instead; an overlay can only be brought to the front by raising its host, which would hide the dialog already there. And a modalNATIVE_WINDOWdialog over a fullscreen window is drawn in scene, for the reasonsDisplayModegives, and one already a window moves into the scene when its owner goes fullscreen; a non-modal one stays a window, having no in-scene form.displayMode()answers what actually happened;keepInScene()insists on the overlay.- Parameters:
mode- the presentation asked for- Returns:
- this dialog
-
displayMode
How this dialog is actually presented: the requested mode untilshow(limn.scene.Scene)decides otherwise, and the effective one afterwards, which changes once more, toDisplayMode.IN_SCENE, if a modal dialog's owner goes fullscreen while it is a window.Worth asking after
showby anything that styles or positions the card on the assumption that it is one or the other: a promoted dialog is a real window, sosetStylestarts meaning something and an in-scene drag affordance stops, and a modal dialog raised over a fullscreen window is an overlay whatever was asked, so the reverse. Until this existed the promotion was announced only in a log line, which no application reads. -
keepInScene
Insists onDisplayMode.IN_SCENEeven when a modal window is already open over the host, the case this dialog would otherwise be promoted out of, silently and with a warning.Say this when you have looked at the stacking and are content with it: the overlay will be answerable, but bringing its host window forward hides any dialog floating over it, so the earlier one appears to vanish until this one closes. The honest reason to accept that is not needing an extra OS window at all: an embedded or kiosk surface, or a host that cannot create one.
No boolean: promotion is the default and this is an assertion, not a property.
keepInScene(false)would be a way to write "promote me, probably", which is just the default spelled less clearly. -
setAlwaysOnTop
Whether the native dialog window floats above other windows (defaulttrue). Only applies toDisplayMode.NATIVE_WINDOW.- Parameters:
value- whether the window floats above others- Returns:
- this dialog
-
setDismissOnScrim
Whether clicking the scrim dismisses the dialog (defaultfalse). Only applies toDisplayMode.IN_SCENE. Whenfalsethe dialog is truly modal: a scrim click is ignored and answered with the same alert feedback (beep) a native modal gives; whentrueit closes with the cancel result (a dismissable overlay), askingonCancelfirst when it is set.- Parameters:
value- whether a scrim click dismisses- Returns:
- this dialog
-
setStyle
Sets the native window framing (defaultWindowStyle.UNDECORATED_TRANSLUCENT):DECORATEDuses the OS title bar/border (opaque);UNDECORATED_OPAQUEis a borderless solid card; the default is a borderless glassy panel with rounded, see-through corners. Ignored inDisplayMode.IN_SCENE(the internal card is always the glassy panel).- Parameters:
newStyle- the framing- Returns:
- this dialog
-
dismiss
Programmatically closes the dialog, resolving withresult. UI thread only.Never asks the
onCancelhandler, whatever the result, and settles a request that handler holds: it is how a handler gives an answer other than the cancel result.- Parameters:
result- what the dialog resolves with; may be null
-
result
- Returns:
- the stage that completes when the dialog closes (same as
show(limn.scene.Scene))
-
fadeLevel
public float fadeLevel()- Returns:
- the current fade level in [0..1] (tests/screenshots)
-
modalWindow
- Returns:
- the modal's native window while open (screenshots/tests), else null, which it also is once a modal dialog has moved into the scene of an owner that went fullscreen
-
show
Shows the dialog window-modal overowner(locks only its window), or, inDisplayMode.IN_SCENEand over a fullscreen owner, a scene-modal internal overlay.The card takes its size step, layout direction and locale from the scene's root, and from the new root when
Scene.setRoot(limn.scene.Widget<?>)switches the page while it is open, as everySceneoverload of this class does; theWidgetoverloads take them from the widget given, which stays the card's host whatever happens to the page around it.- Parameters:
owner- the scene the dialog is modal over- Returns:
- a stage that completes with the result when the dialog closes
- Throws:
IllegalStateException- if this dialog was already shown (dialogs are single-use)
-
showToolkitModal
Shows the dialog toolkit-modal overowner(locks every window). InDisplayMode.IN_SCENE, and over a fullscreen owner, it presents the same scene-modal internal overlay asshow(limn.scene.Scene), which still locks every other window.- Parameters:
owner- the scene the dialog opens over- Returns:
- a stage that completes with the result when the dialog closes
- Throws:
IllegalStateException- if this dialog was already shown (dialogs are single-use)
-
showNonModal
Shows the dialog non-modal overowner: a floating window that locks nothing, so the owner (and every other window) stays fully interactive. Only valid inDisplayMode.NATIVE_WINDOW; an in-scene overlay always captures its scene's input. For the same reason it stays a window even over a fullscreen owner, where every other floating surface is drawn in scene, and when its owner goes fullscreen while it is open, and there it pays what a second window costs: an owner fullscreen at a named mode is minimized when the dialog takes the focus, and on a Mac without a GPU the window is slow to open.- Parameters:
owner- the scene the dialog opens over- Returns:
- a stage that completes with the result when the dialog closes
- Throws:
IllegalStateException- if the display mode isDisplayMode.IN_SCENE, or if this dialog was already shown (dialogs are single-use)
-
show
show(Scene)overowner's scene, but inheriting the size step fromowneritself rather than from the scene root, the overload to reach for when a SMALL toolbar (or a LARGE settings pane) raises the dialog and the card should match the surface it came from.The dialog keeps
owneras its host until it closes, even ifownerleaves the window meanwhile (taken out of its container, or with the pageScene.setRoot(limn.scene.Widget<?>)replaces): unlike a menu or a popover shown at a widget, it is not closed when the widget goes, since it is a question still waiting for its answer, and its card goes on taking its size step, direction and locale fromowner. The widget, and the page around it, therefore stay reachable for as long as the dialog is open. A dialog that should follow the page shown in the window is shown on theSceneinstead.- Parameters:
owner- the widget whose scene the dialog is modal over, and whose step it takes- Returns:
- a stage that completes with the result when the dialog closes
- Throws:
IllegalStateException- ifowneris not in a scene, or the dialog was already shown
-
showToolkitModal
showToolkitModal(Scene), inheriting the size step fromowner, which stays the dialog's host until it closes, asshow(Widget)says.- Parameters:
owner- the widget whose scene the dialog opens over, and whose step it takes- Returns:
- a stage that completes with the result when the dialog closes
-
showNonModal
showNonModal(Scene), inheriting the size step fromowner, which stays the dialog's host until it closes, asshow(Widget)says.- Parameters:
owner- the widget whose scene the dialog opens over, and whose step it takes- Returns:
- a stage that completes with the result when the dialog closes
-