* Add UI Guidelines to SDK docs * Add UI Guidelines to SDK docs * Fixing build errors * optimize PNGs * add UI guidelines landing page placeholder * IJ SDK Docs <-> UI guidelines crosslinks updated * split_button.md: remove reference to removed setting * use <ui-path> * use MD instead of <note> * use %gh-ic% links * drop_down.md: fix <control> * code samples: fix most obvious issues * remove obsolete `_defaults.md` * ijs.tree: UI cleanup * Delete "under construction" pages * Fix headers * Add link-summary * Remove invalid links * Delete unused files * Remove ''@2x' from image file names * Use Markdown syntax for some images and tables * Rename non-unique files to unique * Remove alpha in images where content is unreadable * align quotation marks * Controls: cleanup/fixes, add code links, edit * tooltip.md: fix HTML * misc fixes * typography.md: fix table contents * typography.md: fix table header * UI guidelines landing page + TOC fixes * remove unused icons_list.md * Normalize image paths * validation_errors.md: Fix broken tab * "correct"/"incorrect" labels styling * Resize images to 50% * button.topic: fixes * grammar, spelling, minor edits * remove ' ' * fix 99px * cleanup * UI_kit.md: minor * Fix "MRK058: Large image in paragraph rendered as a block element by default." * button.topic: Add img[alt] * mnemonics.md: Update "Contact Us" link to the IJSDK YouTrack * split_button.md: Use ui-path * UI landing: add feedback snippet * Improve code snippets formatting and naming * Fix code samples * Fix code samples * Add Kotlin variants for code samples * Add icons_list.md * crosslinks * Change external link to https://intellij-icons.jetbrains.design/ * icons list -> https://intellij-icons.jetbrains.design * Hide info about reducing split button to simple action button (now it is available through the registry only) * reformat * icons_style.md: Images in new line --------- Co-authored-by: marianna.kononenko <marianna.kononenko@jetbrains.com> Co-authored-by: Yann Cébron <yann.cebron@jetbrains.com>
21 KiB
Kotlin UI DSL Version 2
Kotlin DSL for creating UI forms with input components bound to state object.
This page describes API available in IntelliJ Platform releases 2021.3 and later only.
{style="warning"}
Kotlin UI DSL Version 2 allows creating UI forms with input components bound to state objects. The forms are built by using a declarative Kotlin syntax and follow the official IntelliJ Platform UI conventions described in the . The library is written in Kotlin and makes it easy to develop user interfaces like dialogs and settings pages.
The Kotlin UI DSL is not intended to build general UIs, like tool windows controls that trigger some actions and do not contain any input components bound to state objects. For this purpose, use custom Swing components from the IntelliJ Platform or the standard ones.
The Kotlin UI DSL Version 2 functions are located in the com.intellij.ui.dsl.builder
package.
UI DSL Examples
It is highly recommended taking a look at the UI DSL demo available via Tools | Internal Actions | UI | UI DSL Showcase (see Internal Actions if not available in your IDE instance).
It describes some UI DSL basics and contains explanations, tips, a list of all available components, and many examples with links to the source code.
All sections below refer to relevant tab available in this demo:
UI DSL Showcase Tab: Tab Name (Link to sources of demonstration tab)
To understand how a component visible in the IDE is created in code, see the component's
added-at
property in the UI Inspector. Note that it is not limited only to components created with Kotlin UI DSL, but helps to understand creation of any visible Swing component.
UI DSL Basics
UI DSL Showcase Tab: Basics (Sources: DemoBasics
)
See the following simple example of UI DSL:
panel {
row("Enter value:") {
textField()
}
}
Building content of any form starts from panel {
which returns DialogPanel
filled with components described inside the panel block.
A panel consists of any number of rows marked with row
tag created vertically from top to bottom.
Every row consists of cells where the last cell in a row occupies the remaining width.
Inside one row, cells are added from left to right in the same order calls to factory methods or cell()
appear in each row.
Cells can contain one component or a sub-panel.
If there are unoccupied cells at the end of a row, they are merged into one cell with the last non-empty cell.
Panel
Panel
is the start interface for building content.
It can consist of several rows and different UI groups.
Panel.row
Adds row with the label if present.
Panel.indent
UI DSL Showcase Tab: Gaps (Sources: DemoGaps
)
Adds standard left indent:
row {
label("Not indented row")
}
indent {
row {
label("Indented row")
}
}
Panel.separator
UI DSL Showcase Tab: Groups (Sources: DemoGroups
)
Adds horizontal line separator with an optional title.
Panel.panel
Creates a sub-panel that occupies the whole width and uses its own grid inside.
Panel.rowsRange
Creates grouped rows range to perform operations on them like enabled
, enabledIf
etc.
All rows use the parent grid.
Panel.group
UI DSL Showcase Tab: Groups (Sources: DemoGroups
)
Adds a panel with an independent grid, optional title, and some vertical space above and below the group.
group("Title") {
row("Row:") {
textField()
}
}
Panel.groupRowsRange
Similar to Panel.group()
method but uses the same grid as the parent.
Panel.collapsibleGroup
UI DSL Showcase Tab: Groups (Sources: DemoGroups
)
Adds collapsible panel with independent grid, title, and some vertical space above and below the group.
collapsibleGroup("Title") {
row("Row:") {
textField()
}
}
Panel.buttonsGroup
UI DSL Showcase Tab: Groups (Sources: DemoGroups
)
Unions Row.radioButton
in one group.
Must also be used for Row.checkBox
if these are grouped with some title.
var value = true
buttonsGroup("Panel.buttonsGroup:") {
row {
radioButton("true", true)
}
row {
radioButton("false", false)
}
}.bind({ value }, { value = it })
Panel.onApply
/onReset
/onIsModified
Registers callbacks that will be called from DialogPanel.apply()
/reset()
/isModified()
methods.
Row
Every row is represented by the Row
interface.
It contains all available factory methods for creating components (like button()
, label()
, textField()
, etc.) and methods for row configuration.
Row.layout
UI DSL Showcase Tab: Row Layout (Sources: DemoRowLayout
)
There are three possible layouts:
INDEPENDENT
: all cells of the row (including label if present) independent of the parent grid. That means this row has its own grid.LABEL_ALIGNED
: label is aligned with the parent grid, other components independent of the parent grid.PARENT_GRID
: all components, including label (if present), are in the parent grid.
The default value is LABEL_ALIGNED
when a label is provided for the row, INDEPENDENT
otherwise.
row("Label:") {
textField()
}
row("Long label:") {
textField()
}
Here both labels are aligned together because rows have LABEL_ALIGNED
by default.
If an independent layout is needed, then INDEPENDENT
layout should be used:
row("Long label:") {
textField()
}.layout(RowLayout.INDEPENDENT)
Row.resizableRow
The row becomes resizable and occupies all vertical free space. For several resizable rows, extra free space is divided between rows equally.
Row.rowComment
UI DSL Showcase Tab: Comments (Sources: DemoComments
)
Adds comment after the row with appropriate color and font. Visibility and enabled state of the row affects row comment as well.
Row.cell
Adds component
.
Use it only for custom specific components, all standard components like label()
, button()
, checkbox()
etc. are covered by dedicated Row
factory methods.
For example, there is no method for password field so that the following code can be used:
val passwordField = JPasswordField()
row {
cell(passwordField)
}
Row.scrollCell(component)
Adds component
wrapped with JBScrollPane
.
Row.topGap
/bottomGap
Adds additional gap above/below the current row.
It is visible together with the row.
By default, NONE
is used.
Between unrelated settings, SMALL
can be used.
MEDIUM
is used between groups and usually set automatically by group()
method and similar ones.
Row.visible
/enabled
UI DSL Showcase Tab: Enabled/Visible (Sources: DemoAvailability
)
Sets visibility/enabled state of the row, including row comment (see Row.rowComment
) and all children recursively.
The row is invisible/disabled if there is an invisible/disabled parent.
Row.visibleIf
/enabledIf
UI DSL Showcase Tab: Enabled/Visible (Sources: DemoAvailability
)
Binds row visibility/enabled state to the provided predicate. Below is an example of a checkbox whose enabled state depends on another checkbox:
lateinit var checkBox: Cell<JBCheckBox>
row {
checkBox = checkBox("Check to enable option")
}
row {
checkBox("Option 1")
}.enabledIf(checkBox.selected)
Row.panel
UI DSL Showcase Tab: Groups (Sources: DemoGroups
)
Creates a sub-panel inside the cell of the row. The panel contains its own set of rows and cells. For example, it is possible to create several columns by creating a row with several panels inside.
Cell
Every component in the UI DSL builder is wrapped into Cell
class.
Standard components should not be created directly but with factory methods from Row
class like checkBox()
, button()
and others because of additional functionality, e.g. textField()
is configured with columns width, radio buttons are placed into radio buttons groups.
Cell.component
JComponent
that occupies the cell.
Cell.horizontalAlign
/verticalAlign
{#cell-horizontalVerticalAlign}
Sets horizontal/vertical alignment of content inside the cell.
row("Row:") {
textField()
.horizontalAlign(HorizontalAlign.FILL)
}
Cell.align
{#cell-align}
2022.3
Updates horizontal and/or vertical alignment of the component inside the cell.
Default alignment is AlignX.LEFT
and AlignY.CENTER
.
To stretch the content on whole cell, use AlignX.FILL
/AlignY.FILL
/Align.FILL
.
For setting both horizontal and vertical alignment, use Align
constants or overloaded plug operator
like align(AlignX.LEFT + AlignY.TOP)
.
row("Row:") {
textField()
.align(AlignX.FILL)
}
Cell.resizableColumn
UI DSL Showcase Tab: Tips (Sources: DemoTips
)
Marks column of the cell as resizable: the column occupies all extra horizontal space in panel and changes size together with the panel. It's possible to have several resizable columns, which means extra space is shared between them. There is no need to set resizable for cells in different rows but in the same column: it has no additional effect. Note that the size and placement of components in columns are managed by ( for pre-2022.3).
row("Row") {
textField()
.resizableColumn()
link("Config...") {}
}
Cell.gap
UI DSL Showcase Tab: Gaps (Sources: DemoGaps
)
Separates the next cell in the current row with rightGap
.
RightGap.SMALL
gap is set after row label automatically by Panel.row()
methods.
Below are some cases where RightGap.SMALL
should be used:
row {
val checkBox = checkBox("Use mail:")
.gap(RightGap.SMALL)
textField()
}
row("Width:") {
textField()
.gap(RightGap.SMALL)
label("pixels")
}
Cell.visible
/enabled
UI DSL Showcase Tab: Enabled/Visible (Sources: DemoAvailability
)
Sets visibility/enabled state of the cell and all children recursively. The cell is invisible/disabled if there is an invisible/disabled parent.
Cell.visibleIf
/enabledIf
UI DSL Showcase Tab: Enabled/Visible (Sources: DemoAvailability
)
Binds cell visibility/enabled state to the provided predicate.
row {
val mailCheckBox = checkBox("Use mail:")
.gap(RightGap.SMALL)
textField()
.enabledIf(mailCheckBox.selected)
}
Cell.comment
UI DSL Showcase Tab: Comments (Sources: DemoComments
)
Adds comment under the cell aligned by left edge with appropriate color and font size (macOS uses smaller font).
Comment can contain HTML tags except <html>
, which is added automatically.
The comment occupies the available width before the following comment (if present) or the whole remaining width.
Visibility and enabled state of the cell affect comment as well.
row("Label:") {
textField()
.comment("Comment for textField")
}
Cell.label
UI DSL Showcase Tab: Components Labels (Sources: DemoComponentLabels
)
Adds label at the specified position.
LabelPosition.TOP
labels occupy available width before the next top label (if present) or the whole remaining width.
Visibility and enabled state of the cell affect the label as well.
row {
textField()
.label("Cell label on top:", LabelPosition.TOP)
}
Cell.onApply
/onReset
/onIsModified
Registers callbacks that will be called for component from DialogPanel.apply()
/reset()
/isModified()
methods.
Placeholder
Used as a reserved cell in the layout.
It can be created by Row.placeholder()
method and populated by content later via component property or reset to null
.
Bindings
It is possible to bind component values to properties with the following methods.
Cell.graphProperty
Removed in 2023.3, please use
validationRequestor(property::afterPropagation)
instead.
Binds component
value changing to property.
The property is updated when the value is changed and is not related to DialogPanel.apply()
.
The method is rarely used directly.
There are many extensions for specific components described in .
Cell.bind
UI DSL Showcase Tab: Binding (Sources: DemoBinding
)
Binds component
value that is provided by componentGet
and componentSet
methods to specified binding property.
The property is applied only when DialogPanel.apply()
is invoked.
Methods DialogPanel.isModified()
and DialogPanel.reset()
are also supported automatically for bound properties.
The bind()
method is rarely used directly.
There are many extensions for specific components, see following example:
row {
checkBox("Checkbox")
.bindSelected(model::checkbox)
}
row("textField:") {
textField()
.bindText(model::textField)
}
row("intTextField(0..100):") {
intTextField()
.bindIntText(model::intTextField)
}
row("comboBox:") {
comboBox(Color.values())
.bindItem(model::comboBoxColor)
}
row("slider:") {
slider(0, 100, 10, 50)
.bindValue(model::slider)
}
row("spinner:") {
spinner(0..100)
.bindIntValue(model::spinner)
}
buttonsGroup(title = "radioButton:") {
for (value in Color.values()) {
row {
radioButton(value.name, value)
}
}
}.bind(model::radioButtonColor)
Version 1 and 2 Comparison
In UI DSL version 2, some crucial problems from version 1 have been fixed, so porting is highly desirable. See on how to port existing UI DSL code from version 1 to version 2 API. Version 1 is deprecated and will be removed in future platform releases.
The following significant changes were made:
- Reduced API, which allows conceiving API easier and faster.
Example: there were 5 overloaded methods
Cell.checkBox()
in version 1, now only one method remains. Functionality for binding properties is extracted intoCell<T>.bindSelected()
methods. - UI DSL became stricter, so the available API in every context is much smaller.
Example: code like
row { row {
is forbidden now. - Structured API mostly based on interfaces, because it's easier to learn API by grouped methods. Only a small part of API is implemented as extensions.
- KDoc is widely used.
- MIG layout is fully removed from the new UI DSL and replaced by
GridLayout
. Because MIG layout is an external library, it's hard to fix bugs there (e.g., there are layout problems when components become invisible) and extend its functionality. Fixed focus ring cropping problems: when components are placed near the panel border focus ring could be cropped if panel insets do not specify enough space. - Implemented Placeholder that allows replacing components at runtime after content is shown.
Migration from Version 1
New API is very similar to the old one and covers almost all functionality now, so moving to the new version can be done quickly.
Version 1 is placed in com.intellij.ui.layout
package.
Version 2 is placed in com.intellij.ui.dsl.builder
package.
- Having a screenshot or live version of the initial components layout can help
- Remove imports of old UI DSL starting with
com.intellij.ui.layout
- Go to the place where the panel is created and import new UI DSL
com.intellij.ui.dsl.builder
suggested by IntelliJ IDEA - Update non-compilable code, using the following table
Version 1 | Version 2 |
---|---|
row { row { |
indent { row { |
row { cell(isFullWidth = true) |
row { |
fullRow { |
row { |
titledRow(…) { |
group(…) { |
hideableRow |
collapsibleGroup |
cell used as sub-grid |
row { panel { … } } |
component(…) or its invocation via () |
cell(…) |
enableIf |
enabledIf |
checkBox(text, bindOptions) |
checkBox(text).bindSelected(bindOptions) |
radioButton(text, bindOptions) |
radioButton(text).bindSelected(bindOptions) |
comboBox(…, bindOptions) |
comboBox(text).bindItem(bindOptions) |
textField(bindOptions, columns) |
textField().bindText(bindOptions).columns(columns) |
scrollableTextArea(bindOptions, rows, columns) |
textArea().bindText(bindOptions).rows(rows).columns(columns) |
intTextField(bindOptions, columns, range, step) |
intTextField(range, step).bindIntText(bindOptions).columns(columns) |
textFieldWithBrowseButton(bindOptions, …) |
textFieldWithBrowseButton(…).bindText(bindOptions) |
.growPolicy(GrowPolicy.COLUMNS_SHORT) |
.columns(SHORT_TEXT) |
.growPolicy(GrowPolicy.MEDIUM_TEXT) |
.columns(COLUMNS_MEDIUM) |
label(…, bold = true) |
label(…).bold() |
withLeftGap() |
For previous left cell use Cell.gap(SMALL) |
withLeftGap(gapLeft) |
Please do not use custom gaps if possible |
withLargeLeftGap() |
Not needed, this gap is set by default |
withValidationOnInput |
validationOnInput |
withValidationOnApply |
validationOnApply |
withErrorOnApplyIf |
errorOnApply |
withBinding |
bind |