Form controls
Doodle has many built-in controls that cover a range of data types for forms. These controls come as functions that return FieldVisualizer
s. They are all found within the io.nacular.doodle.controls.form
package.
Usage of these controls still require explicit installation of Behavior
s. Those shown below use behaviors from the BasicTheme
orNativeTheme
(Web, Desktop).
Boolean
check
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.check
fun check() {
//sampleStart
check("Some question")
//sampleEnd
}
Name | Type | Description |
---|---|---|
label | View | String |
Type Boolean
, View: CheckBox
Check-box that represents a true/false value.
switch
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.switch
fun switch() {
//sampleStart
switch("Some question")
//sampleEnd
}
Name | Type | Description |
---|---|---|
label | View | String |
Type Boolean
, View: Switch
Switch that represents a true/false value.
T
(interpolatable)
slider
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.slider
fun slider() {
//sampleStart
slider(0 .. 100)
//sampleEnd
}
Name | Type | Description |
---|---|---|
T | Comparable<T> | Sliders work with any interpolatable type |
model | ConfinedValueModel<T> | model used for the Slider |
orientation | Orientation | Slider's orientation |
config | SliderConfig<T>.() | configuration block |
Type T
, View: Slider
<T>
Slider that represents a bounded value.
circularSlider
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.circularSlider
fun circularSlider() {
//sampleStart
circularSlider(0 .. 100)
//sampleEnd
}
Name | Type | Description |
---|---|---|
T | Comparable<T> | Sliders work with any interpolatable type |
model | ConfinedValueModel<T> | model used for the Slider |
config | SliderConfig<T>.() | configuration block |
Type T
, View: CircularSlider
<T>
Slider that represents a bounded value.
ClosedRange<T>
(interpolatable)
rangeSlider
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.rangeSlider
fun rangeSlider() {
//sampleStart
rangeSlider(0 .. 100)
//sampleEnd
}
Name | Type | Description |
---|---|---|
T | Comparable<T> | Sliders work with any interpolatable type |
model | ConfinedRangeModel<T> | model used for the Slider |
orientation | Orientation | Slider's orientation |
config | SliderConfig<T>.() | configuration block |
Type ClosedRange<T>
, View: RangeSlider
<T>
Slider that represents a bounded range.
circularRangeSlider
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.circularRangeSlider
fun circularRangeSlider() {
//sampleStart
circularRangeSlider(0 .. 100)
//sampleEnd
}
Name | Type | Description |
---|---|---|
T | Comparable<T> | Sliders work with any interpolatable type |
model | ConfinedRangeModel<T> | model used for the Slider |
config | SliderConfig<T>.() | configuration block |
Type ClosedRange<T>
, View: CircularRangeSlider
<T>
Slider that represents a bounded range.
T
textField
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.textField
fun textField() {
//sampleStart
textField(pattern = Regex(".+"))
//sampleEnd
}
Requires a TextFieldBehavior
. The module nativeTextFieldBehavior
(Web, Desktop) provides one.
Name | Type | Description |
---|---|---|
pattern | Regex | Initial filter for any text input. Text that fails this will invalidate the field. |
encoder | Encoder<T, String> | Translates text to an instance of T if possible, or invalidates the field. |
validator | (T) -> Boolean | Validates the decoded T . |
config | TextFieldConfig<T>.() -> Unit | Allows configuration of the view. |
There are other signatures for common types like pure text.
Type T
, View: TextField
Text field that can generate a value of types T
.
radioList
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.radioList
fun radioList() {
//sampleStart
radioList("Item1", "Item2", "Item3")
//sampleEnd
}
Name | Type | Description |
---|---|---|
first | T | First option. |
rest | vararg T | Remaining options. |
config | OptionListConfig<T>.() -> Unit | Allows configuration of the view. |
Type T
, View: container of RadioButton
`s
List of buttons that requires selection of a single item.
singleChoiceList
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.singleChoiceList
fun singleChoiceList() {
//sampleStart
singleChoiceList("Item1", "Item2", "Item3")
//sampleEnd
}
Name | Type | Description |
---|---|---|
first | T | First item in the list. |
vararg rest | T | Remaining items in the list. |
itemVisualizer | ItemVisualizer<T, IndexedItem> | Visualizer used for each item in the list. |
fitContents | Boolean | Sets whether the list should fit its contents. |
config | (List<T, LisModel<T>) -> Unit | Allows configuration of the list. |
There are simpler signatures for common data types like text and numbers.
Type T
, View: List
<T>
List that requires selection of a single item.
spinButton
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.spinButton
fun spinButton() {
//sampleStart
spinButton("Item1", "Item2", "Item3")
//sampleEnd
}
Name | Type | Description |
---|---|---|
model | SpinButtonModel<T> | Model used to populate the spinButton. |
itemVisualizer | ItemVisualizer<T, SpinButton<T, M>> | Visualizer used for items in the spinButton. |
config | (SpinButton<T, M>) -> Unit | Allows configuration of the spinButton. |
There are simpler signatures for common data types like text and numbers.
Type T
, View: SpinButton
SpinButton that that requires selection of a single item.
selectBox
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.selectBox
fun selectBox() {
//sampleStart
selectBox("Item1", "Item2", "Item3")
//sampleEnd
}
Name | Type | Description |
---|---|---|
model | ListModel<T> | Model used to populate the list. |
boxItemVisualizer | ItemVisualizer<T, IndexedItem> | Visualizer used for the box item in the select box. |
listItemVisualizer | ItemVisualizer<T, IndexedItem> | Visualizer used for each item in the select box's list. |
config | (SelectBox<T, *>) -> Unit | Allows configuration of the list. |
There are simpler signatures for common data types like text and numbers.
Type T
, View: SelectBox
SelectBox that that requires selection of a single item.
form
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.form
import io.nacular.doodle.controls.form.labeled
import io.nacular.doodle.controls.form.map
import io.nacular.doodle.controls.form.textField
import io.nacular.doodle.utils.ToStringIntEncoder
fun formField() {
//sampleStart
data class Person(val name: String, val age: Int)
form<Person> { this(
initial.map { it.name } to labeled("Name") { textField() },
initial.map { it.age } to labeled("Age" ) { textField(encoder = ToStringIntEncoder) },
onInvalid = {}
) { name, age ->
Person(name, age) // construct result when valid
} }
//sampleEnd
}
Name | Type | Description |
---|---|---|
builder | FormControlBuildContext<T>.() -> FieldVisualizer<T> | Visualizer that creates the form. |
Type T
, View: Form
Sub-form that allows entering of arbitrary fields that will be mapped to a single, strongly typed result T
.
T?
optionalRadioList
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.optionalRadioList
fun optionalRadioList() {
//sampleStart
optionalRadioList("Item1", "Item2", "Item3")
//sampleEnd
}
Name | Type | Description |
---|---|---|
first | T | First option. |
rest | vararg T | Remaining options. |
config | OptionListConfig<T>.() -> Unit | Allows configuration of the view. |
Type T?
, View: container of RadioButton
`s
List of buttons that allows selection of zero or one item.
optionalSingleChoiceList
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.optionalSingleChoiceList
fun optionalSingleChoiceList() {
//sampleStart
optionalSingleChoiceList("Item1", "Item2", "Item3")
//sampleEnd
}
Name | Type | Description |
---|---|---|
model | ListModel<T> | Model used to populate the list. |
itemVisualizer | ItemVisualizer<T, IndexedItem> | Visualizer used for each item in the list. |
fitContents | Boolean | Sets whether the list should fit its contents. |
config | (List<T, LisModel<T>) -> Unit | Allows configuration of the list. |
There are simpler signatures for common data types like text and numbers.
Type T?
, View: List
<T>
List that allows selection of zero or one item.
optionalSelectBox
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.optionalSelectBox
fun optionalSelectBox() {
//sampleStart
optionalSelectBox("Item1", "Item2", "Item3", unselectedLabel = "")
//sampleEnd
}
Name | Type | Description |
---|---|---|
model | ListModel<T> | Model used to populate the list. |
boxItemVisualizer | ItemVisualizer<T, IndexedItem> | Visualizer used for the box item in the select box. |
listItemVisualizer | ItemVisualizer<T, IndexedItem> | Visualizer used for each item in the select box's list. |
unselectedBoxItemVisualizer | ItemVisualizer<Unit, IndexedItem> | Visualizer used for the box item when nothing selected. |
unselectedListItemVisualizer | ItemVisualizer<Unit, IndexedItem> | Visualizer used for the "select none" item in the select box's list. |
config | (SelectBox<T, *>) -> Unit | Allows configuration of the list. |
There are simpler signatures for common data types like text and numbers.
Type T?
, View: SelectBox
SelectBox that that allows selection of zero or one item.
List<T>
list
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.list
fun list() {
//sampleStart
list("Item1", "Item2", "Item3")
//sampleEnd
}
Name | Type | Description |
---|---|---|
model | ListModel<T> | Model used to populate the list. |
itemVisualizer | ItemVisualizer<T, IndexedItem> | Visualizer used for each item in the list. |
fitContents | Boolean | Sets whether the list should fit its contents. |
config | (List<T, LisModel<T>) -> Unit | Allows configuration of the list. |
There are simpler signatures for common data types like text and numbers.
Type List<T>
, View: List
<T>
List that allows selection of zero or more items.
checkList
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.check
fun check() {
//sampleStart
check("Some question")
//sampleEnd
}
Name | Type | Description |
---|---|---|
first | T | First option. |
rest | vararg T | Remaining options. |
config | OptionListConfig<T>.() -> Unit | Allows configuration of the view. |
switchList
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.switchList
fun switchList() {
//sampleStart
switchList("Item1", "Item2", "Item3")
//sampleEnd
}
Name | Type | Description |
---|---|---|
first | T | First option. |
rest | vararg T | Remaining options. |
config | OptionListConfig<T>.() -> Unit | Allows configuration of the view. |
LocalFile
file
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.file
import io.nacular.doodle.datatransport.PlainText
fun file() {
//sampleStart
file(acceptedTypes = setOf(PlainText))
//sampleEnd
}
Name | Type | Description |
---|---|---|
acceptedTypes | Set<MimeType<*>> | File types to allow |
Type LocalFile
, View: FileSelector
FileSelector that represents a local file.
List<LocalFile>
files
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.files
import io.nacular.doodle.datatransport.PlainText
fun files() {
//sampleStart
files(acceptedTypes = setOf(PlainText))
//sampleEnd
}
Name | Type | Description |
---|---|---|
acceptedTypes | Set<MimeType<*>> | File types to allow |
Type List<
LocalFile
>
, View: FileSelector
FileSelector that represents a list of local files.
Structure
labeled
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.field
import io.nacular.doodle.controls.form.labeled
import io.nacular.doodle.core.view
fun <T> labeled() {
//sampleStart
labeled("Name", help = "Help text") {
field<T> {
view {}
}
}
//sampleEnd
}
Name | Type | Description |
---|---|---|
name | StyledText | To display for the field. |
help | StyledText | Optional help text to display along with the field. |
showRequired | RequiredIndicatorStyle? | Defines whether or how to indicate the field is required using a text decorator. |
visualizer | LabeledConfig.() -> FieldVisualizer<T> | Visualizer that creates the wrapped control. |
Some of these parameters are optional.
Type T
, View: Container
Wrapper around a control that adds name and other labeling.
scrolling
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.field
import io.nacular.doodle.controls.form.scrolling
import io.nacular.doodle.core.view
import io.nacular.doodle.geometry.Size
fun <T> scrolling() {
//sampleStart
scrolling {
scrollPanel.idealSize = Size(100)
field<T> {
view {}
}
}
//sampleEnd
}
Name | Type | Description |
---|---|---|
visualizer | ScrollingConfig.() -> FieldVisualizer<T> | Visualizer that creates the wrapped control. |
Type T
, View: ScrollPanel
ScrollPanel wrapper for a control that allows scrolling.
framed
- Demo
- Basic Usage
- Parameters
package forms
import io.nacular.doodle.controls.form.field
import io.nacular.doodle.controls.form.framed
import io.nacular.doodle.core.view
import io.nacular.doodle.drawing.Color.Companion.Lightgray
import io.nacular.doodle.drawing.paint
import io.nacular.doodle.layout.Insets
fun <T> framed() {
//sampleStart
framed {
// configure the container builder
insets = Insets(10.0)
render = {
rect(bounds.atOrigin, radius = 10.0, fill = Lightgray.paint)
}
field<T> {
view {}
}
}
//sampleEnd
}
Name | Type | Description |
---|---|---|
visualizer | ContainerBuilder.() -> FieldVisualizer<T> | Visualizer that creates the wrapped control. |
Type T
, View: Container
Container wrapper for a control that can be styled and laid out.
Custom Controls
Doodle makes it easy to create custom form controls. You simply implement FieldVisualizer
or use the field
DSL. Each control is a simple binding between a Field
, its initial value and a View
.
Here is an example of how you might add a boolean
field with some text.
package forms
import io.nacular.doodle.controls.buttons.Switch
import io.nacular.doodle.controls.form.Form.Valid
import io.nacular.doodle.controls.form.field
import io.nacular.doodle.controls.form.ifValid
import io.nacular.doodle.controls.text.Label
import io.nacular.doodle.core.container
import io.nacular.doodle.geometry.Size
import io.nacular.doodle.layout.constraints.constrain
import io.nacular.doodle.text.StyledText
import io.nacular.doodle.utils.Dimension.Height
import io.nacular.doodle.utils.TextAlignment.Start
//sampleStart
fun switchField(text: StyledText) = field {
container {
focusable = false // ensure wrapping container isn't focusable
this += Label(text).apply {
fitText = setOf(Height)
textAlignment = Start
}
this += Switch().apply {
initial.ifValid { selected = it } // adopt initial value if present
selectedChanged += { _,_,_ ->
state = Valid(selected) // update field as switch changes
}
size = Size(40, 25)
state = Valid(selected) // ensure field is valid at beginning
}
layout = constrain(children[0], children[1]) { label, switch ->
switch.left eq parent.right - 10 - switch.width.readOnly
switch.centerY eq parent.centerY
label.left eq 10
label.right eq switch.left - 10
label.centerY eq switch.centerY
}
}
}
//sampleEnd
Here's an example of a simple color strip.
- Demo
- Implementation
- Usage
package forms
import io.nacular.doodle.controls.buttons.ButtonGroup
import io.nacular.doodle.controls.buttons.ToggleButton
import io.nacular.doodle.controls.form.Form.Valid
import io.nacular.doodle.controls.form.field
import io.nacular.doodle.controls.form.ifValid
import io.nacular.doodle.controls.theme.simpleButtonRenderer
import io.nacular.doodle.core.container
import io.nacular.doodle.drawing.Color
import io.nacular.doodle.drawing.Stroke
import io.nacular.doodle.drawing.lighter
import io.nacular.doodle.drawing.rect
import io.nacular.doodle.geometry.Size
import io.nacular.doodle.layout.HorizontalFlowLayout
//sampleStart
fun colorStrip(first: Color, second: Color, vararg rest: Color, size: Double = 30.0) = field {
container {
focusable = false // ensure wrapping container isn't focusable
val buttonGroup = ButtonGroup()
this += listOf(first, second, *rest).map { color ->
// use toggle button since it has desirable selection behavior
ToggleButton().apply {
this.size = Size(size)
behavior = simpleButtonRenderer { button, canvas ->
val radius = width * 0.2
val thickness = 2.0
val fillColor = if (button.model.pointerOver) color.lighter() else color
when {
selected -> canvas.rect(bounds.atOrigin.inset(thickness / 2), radius = radius, color = fillColor, stroke = Stroke(color.inverted, thickness = thickness))
else -> canvas.rect(bounds.atOrigin, radius = radius, color = fillColor)
}
}
acceptsThemes = false
selectedChanged += { _,_,_ ->
if (selected) {
state = Valid(color) // update field state when selected
}
}
initial.ifValid {
selected = it == color // adopt initial value if it matches something in the list
}
buttonGroup += this // ensure only 1 item is ever selected
}
}
layout = HorizontalFlowLayout(spacing = 4.0)
}
}
//sampleEnd
package forms
import io.nacular.doodle.controls.form.Form
import io.nacular.doodle.docs.utils.BlueColor
import io.nacular.doodle.drawing.Color.Companion.Black
import io.nacular.doodle.drawing.Color.Companion.Blue
import io.nacular.doodle.drawing.Color.Companion.Green
import io.nacular.doodle.drawing.Color.Companion.Red
fun colorStripUsage() {
//sampleStart
Form { this(
Green to colorStrip(Red, Green, Blue, BlueColor, Black),
onInvalid = {}
) { color ->
println("Form valid: $color")
} }
//sampleEnd
}