Dialogs
Package: dev.anchorlight.stonelib.dialog
Paper's Dialog API shows native forms: text fields, sliders, checkboxes, and dropdowns, with buttons. FormDialog is a builder for the most common shape, a form with a submit button and an optional cancel button, and FormResponse reads what came back without trusting it.
Example
FormDialog.builder(this, Component.text("Edit pet"))
.body(Component.text("Give your pet a name and a size."))
.text("name", Component.text("Name"), field -> field.initial(pet.name()).maxLength(32))
.slider("scale", Component.text("Size"), 0.5f, 2.0f, field -> field.initial(pet.scale()).step(0.1f))
.toggle("visible", Component.text("Visible to others"), pet.visible())
.dropdown("colour", Component.text("Collar"), List.of(
FormDialog.Builder.option("red", Component.text("Red"), true),
FormDialog.Builder.option("blue", Component.text("Blue"), false)))
.onSubmit(Component.text("Save"), response -> {
String name = UntrustedText.forDisplay(response.text("name", pet.name()), 32);
float scale = response.number("scale", 0.5f, 2.0f, 1.0f);
boolean visible = response.flag("visible", true);
String colour = response.option("colour", "red");
pets.update(response.player(), name, scale, visible, colour);
})
.onCancel(Component.text("Cancel"), player -> {})
.show(player);
FormDialog
| Method | Meaning |
|---|---|
FormDialog.builder(Plugin, Component title) | Starts a form. |
FormDialog.available() | Whether this server has the Dialog API. Check once on enable. |
Builder
| Method | Meaning |
|---|---|
body(Component) | Adds a paragraph of text above the inputs. Call more than once for several paragraphs. |
text(key, label, UnaryOperator<TextDialogInput.Builder>) | A single-line text field, customised through Paper's builder, such as initial and maxLength. |
text(key, label, String initial) | A text field with just an initial value. |
slider(key, label, float min, float max, UnaryOperator<NumberRangeDialogInput.Builder>) | A numeric slider, customised through Paper's builder, such as initial and step. Throws if min is not below max. |
toggle(key, label, boolean initial) | A checkbox. |
dropdown(key, label, List<SingleOptionDialogInput.OptionEntry>) | A dropdown. Throws if the list is empty. |
Builder.option(String id, Component display, boolean initial) | Makes one dropdown option. |
onSubmit(Component label, Consumer<FormResponse>) | The submit button's label and handler. Defaults to a Confirm button that does nothing. |
onCancel(Component label, Consumer<Player>) | Adds a cancel button. Without this call the form has only the submit button. |
canCloseWithEscape(boolean) | Whether Escape closes the form without submitting. Defaults to true. |
show(Player) | Builds and shows the form. Returns false if it could not be shown. |
Inputs appear in the order you add them. Input keys must be non-blank and unique within a form; a blank or repeated key throws IllegalArgumentException as the form is built, where the mistake is obvious, rather than silently shadowing another value.
A builder is for one showing. Build a new form each time.
Handling failure
show catches everything and returns false, logging a warning, if the Dialog API is missing or the dialog cannot be shown. Fall back to something the player can still use:
if (!form.show(player)) {
messages.send(player, "dialog_unavailable");
}
Paper marks the Dialog API as experimental, so its classes can move between versions. That is why show and the button callbacks catch Throwable rather than Exception: a moved class surfaces as NoClassDefFoundError, which must not escape into Paper.
Callbacks
- Handlers always receive a
Player. A click from anything that is not a player is ignored. - A handler that throws is logged as
Dialog callback failed for <player>and does not propagate. - StoneLib does not move handlers to the main thread. If a handler touches the world, check
Bukkit.isPrimaryThread()or hop withSchedulerService.runSync.
FormResponse
The client decides what it sends back. A key can be missing, a slider value can be outside its range, and a text field can contain anything. Every getter therefore takes a fallback.
| Method | Returns |
|---|---|
player() | The player who submitted. |
text(key, String fallback) | The text, or fallback if missing or blank. |
flag(key, boolean fallback) | The toggle value, or fallback if missing. |
number(key, float min, float max, float fallback) | The slider value clamped into min to max, or fallback if missing or not a number. |
option(key, String fallback) | The selected dropdown id, or fallback if missing or blank. |
raw() | Paper's underlying DialogResponseView, for anything this wrapper does not cover. |
:::caution Text fields are player input
Treat text like chat. Run it through UntrustedText.forDisplay before it reaches a MiniMessage template or a display name, and validate it before it reaches storage.
:::
Pass the same bounds to number that you gave slider. The slider range only limits what an honest client offers.