This guide should help you get started with Glimmer DSL for SWT. For more advanced SWT details, please refer to the SWT Reference.
- Glimmer GUI DSL Syntax
- DSL Auto-Expansion
- Widgets
- Glimmer GUI DSL Keywords
- SWT Proxies
- Dialog
- Display
- Multi-Threading
- Menus
- Tray Item
- ScrolledComposite
- Sash Form Widget
- Browser Widget
- Widget Styles
- Explicit SWT Style Bit
- Negative SWT Style Bits
- Extra SWT Styles
- Widget Properties
- Color
- Font
- Image
- Image Options
- Cursor
- Layouts
- Layout Data
- Canvas Shape DSL
- Shapes inside a Shape
- Shapes inside a Widget
- Shapes inside an Image
- Custom Shapes
- Canvas Shape API
- Pixel Graphics
- Canvas Path DSL
- Canvas Transform DSL
- Top-Level Transform Fluent Interface
- Canvas Animation DSL
- Animation via Data-Binding
- Data-Binding
- General Examples
- Shine
- Combo
- List
- Table
- Tree
- DateTime
- Observer
- Observing Widgets
- Observing Models
- Custom Widgets
- Simple Example
- Custom Widget Lifecycle Hooks
- Lifecycle Hooks Example
- Custom Widget API
- Content/Options Example
- Custom Widget Gotchas
- Built-In Custom Widgets
- Custom Widget Final Notes
- Custom Shells
- Drag and Drop
- Miscellaneous
- Multi-DSL Support
- Application Menu Items (About/Preferences)
- App Name and Version
- Performance Profiling
- License
Glimmer GUI DSL Syntax
Glimmer's core is a GUI DSL with a lightweight visual syntax that makes it easy to visualize the nesting of widgets in the GUI hierarchy tree.
It is available through mixing in the Glimmer module, which makes Glimmer GUI DSL Keywords available to both the instance scope and class scope:
include Glimmer
For example, here is the basic "Hello, World!" sample code (you may copy/paste in girb):
include Glimmer
shell {
text "Glimmer"
label {
text "Hello, World!"
}
}.open
The include Glimmer declaration on top mixed the Glimmer module into the Ruby global main object making the Glimmer GUI DSL available at the top-level global scope.
While this works well enough for mini-samples, it is better to isolate Glimmer in a class or module during production application development to create a clean separation between view code (GUI) and model code (business domain). Here is the "Hello, World!" sample re-written in a class to illustrate how mixing in the Glimmer module (via include Glimmer) makes the Glimmer GUI DSL available in both the instance scope and class scope. That is clearly demonstrated by pre-initializing a color constant in the class scope and building the GUI in the #open instance method (you may copy/paste in girb):
class HelloWorld
include Glimmer # makes the GUI DSL available in both the class scope and instance scope
COLOR_FOREGROUND_DEFAULT = rgb(255, 0, 0) # rgb is a GUI DSL keyword used in the class scope
def open
# the following are GUI DSL keywords (shell, text, and label) used in the instance scope
shell {
text "Glimmer"
label {
text "Hello, World!"
foreground COLOR_FOREGROUND_DEFAULT
}
}.open
end
end
HelloWorld.new.open
This renders "Hello, World!" with a red foreground color:

The GUI DSL intentionally avoids overly verbose syntax, requiring as little declarative code as possible to describe what GUI to render, how to style it, and what properties to data-bind to the Models.
As such, it breaks off from Ruby's convention of using do end for multi-line blocks, opting instead for the lightweight and visual { } curly brace blocks everywhere inside the GUI DSL. More details about Glimmer's syntax conventions may be found in the Glimmer Style Guide
Glimmer DSL syntax consists mainly of:
- keywords (e.g.
tablefor a table widget) - style/args (e.g. :multi as in
table(:multi)for a multi-line selection table widget) - content (e.g.
{ table_column { text 'Name'} }as intable(:multi) { table_column { text 'name'} }for a multi-line selection table widget with a table column having header text property'Name'as content)
DSL Auto-Expansion
Glimmer supports a new and radical Ruby DSL concept called DSL Auto-Expansion. To explain, let's first mention the two types of Glimmer GUI DSL keywords: static and dynamic.
Static keywords are pre-identified keywords in the Glimmer DSL, such as shell, display, message_box, async_exec, sync_exec, and bind.
Dynamic keywords are dynamically figured out from currently imported (aka required/loaded) SWT widgets and custom widgets. Examples are: label, combo, and list for SWT widgets and c_date_time, video, and gantt_chart for custom widgets.
The only reason to distinguish between the two is to realize that importing new Glimmer custom widgets and Java SWT custom widget libraries automatically expands Glimmer's DSL vocabulary with new dynamic keywords.
For example, if a project adds this custom Java SWT library from the Nebula Project:
https://www.eclipse.org/nebula/widgets/gallery/gallery.php
Glimmer will automatically support using the keyword gallery
This is what DSL Auto-Expansion is.
You will learn more about widgets next.
Widgets
Glimmer GUIs (user interfaces) are modeled with widgets, which are wrappers around the SWT library widgets found here:
https://www.eclipse.org/swt/widgets/
This screenshot taken from the link above should give a glimpse of how SWT widgets look and feel:
In Glimmer DSL, widgets are declared with lowercase underscored names mirroring their SWT names minus the package name.
For example, here are some Glimmer widgets and their SWT counterparts:
shellinstantiatesorg.eclipse.swt.widgets.Shelltextinstantiatesorg.eclipse.swt.widgets.Textbuttoninstantiatesorg.eclipse.swt.widgets.Buttonlabelinstantiatesorg.eclipse.swt.widgets.Labelcompositeinstantiatesorg.eclipse.swt.widgets.Compositetab_folderinstantiatesorg.eclipse.swt.widgets.TabFoldertab_iteminstantiatesorg.eclipse.swt.widgets.TabItemtableinstantiatesorg.eclipse.swt.widgets.Tabletable_columninstantiatesorg.eclipse.swt.widgets.TableColumntreeinstantiatesorg.eclipse.swt.widgets.Treecomboinstantiatesorg.eclipse.swt.widgets.Combolistinstantiatesorg.eclipse.swt.widgets.List
Every widget is sufficiently declared by name, but may optionally be accompanied with:
- SWT style/arguments wrapped by parenthesis according to Glimmer Style Guide (see next section for details).
- Ruby block containing content, which may be properties (e.g.
enabled false) or nested widgets (e.g.table_columnnested insidetable)
For example, if we were to revisit samples/hello/hello_world.rb above (you may copy/paste in girb):
shell {
text "Glimmer"
label {
text "Hello, World!"
}
}.open
Note that shell instantiates the outer shell widget, in other words, the window that houses all of the desktop graphical user interface.
shell is then followed by a block that contains
# ...
text "Glimmer" # text property of shell
label { # label widget declaration as content of shell
text "Hello, World!" # text property of label
}
# ...
The first line declares a property called text, which sets the title of the shell (window) to "Glimmer". Properties always have arguments (not wrapped by parenthesis according to Glimmer Style Guide), such as the text "Glimmer" in this case, and do NOT have a block (this distinguishes them from widget declarations).
The second line declares the label widget, which is followed by a Ruby content block that contains its text property with value "Hello, World!"
The widget block may optionally receive an argument representing the widget proxy object that the block content is for. This is useful in rare cases when the content code needs to refer to parent widget during declaration. You may leave that argument out most of the time and only add when absolutely needed.
Example:
shell {|shell_proxy|
#...
}
Remember that The shell widget is always the outermost widget containing all others in a Glimmer desktop windowed application.
After it is declared, a shell must be opened with the #open method, which can be called on the block directly as in the example above, or by capturing shell in a @shell variable (shown in example below), and calling #open on it independently (recommended in actual apps)
@shell = shell {
# properties and content
# ...
}
@shell.open
It is centered upon initial display and has a minimum width of 130 (can be re-centered when needed with @shell.center method after capturing shell in a @shell variable as per samples)
Check out the samples directory for more examples.
Example from hello_tab.rb sample (you may copy/paste in girb):


shell {
text "Hello, Tab!"
tab_folder {
tab_item {
text "English"
label {
text "Hello, World!"
}
}
tab_item {
text "French"
label {
text "Bonjour Univers!"
}
}
}
}.open
If you are new to Glimmer, you have learned enough to start running some samples directly or by reading through Glimmer GUI DSL Keywords (which list each keyword's samples). Go ahead and run all Glimmer samples, and come back to read the rest in any order you like since this material is more organized like a reference.
If you are an advanced user of Glimmer DSL for SWT and need more widgets, check out the Nebula Project for an extensive list (50+) of high quality custom widgets.
Glimmer GUI DSL Keywords
This is not an exaustive list, but should give you a good start in learning Glimmer GUI DSL keywords, keeping in mind that the full list can be derived from the SWT documentation. More will be explained in the following sections.
Widgets:
button: featured in Hello, Checkbox! / Hello, Button! / Hello, Table! / Hello, Radio Group! / Hello, Radio! / Hello, Message Box! / Hello, List Single Selection! / Hello, List Multi Selection! / Hello, Group! / Hello, Combo! / Hello, Checkbox Group! / Contact Manager / Tic Tac Toe / Loginbrowser: featured in Hello, Browser!calendar: featured in Hello, Date Time!checkbox: featured in Hello, Checkbox Group! / Hello, Checkbox!checkbox_group: featured in Hello, Checkbox Group!combo: featured in Hello, Table! / Hello, Combo!composite: featured in Hello, Composite! / Hello, Radio! / Hello, Computed! / Hello, Checkbox! / Tic Tac Toe / Login / Contact Managercool_bar: featured in Hello, Cool Bar!date: featured in Hello, Table! / Hello, Date Time! / Hello, Custom Shell! / Tic Tac Toedate_drop_down: featured in Hello, Table! / Hello, Date Time!group: featured in Hello, Group! / Contact Managerlabel: featured in Hello, Computed! / Hello, Checkbox Group! / Hello, Checkbox! / Hello, World! / Hello, Table! / Hello, Tab! / Hello, Radio Group! / Hello, Radio! / Hello, Pop Up Context Menu! / Hello, Menu Bar! / Hello, Date Time! / Hello, Custom Widget! / Hello, Custom Shell! / Contact Manager / Loginlist(w/ optional:multiSWT style): featured in Hello, List Single Selection! / Hello, List Multi Selection! / Contact Managermenu: featured in Hello, Menu Bar! / Hello, Pop Up Context Menu! / Hello, Table!menu_bar: featured in Hello, Menu Bar!menu_item: featured in Hello, Table! / Hello, Pop Up Context Menu! / Hello, Menu Bar!message_box: featured in Hello, Table! / Hello, Pop Up Context Menu! / Hello, Message Box! / Hello, Menu Bar!radio: featured in Hello, Radio! / Hello, Group!radio_group: featured in Hello, Radio Group!scrolled_compositeshell: featured in Hello, Checkbox! / Hello, Button! / Hello, Table! / Hello, Tab! / Hello, Radio Group! / Hello, Radio! / Hello, List Single Selection! / Hello, List Multi Selection! / Hello, Group! / Hello, Date Time! / Hello, Custom Shell! / Hello, Computed! / Hello, Combo! / Hello, Checkbox Group! / Contact Manager / Tic Tac Toe / Logintab_folder: featured in Hello, Tab!tab_item: featured in Hello, Tab!table: featured in Hello, Custom Shell! / Hello, Table! / Contact Managertable_column: featured in Hello, Table! / Hello, Custom Shell! / Contact Managertext: featured in Hello, Computed! / Login / Contact Managertime: featured in Hello, Table! / Hello, Date Time!tool_bar: featured in Hello, Tool Bar!tool_item: featured in Hello, Tool Bar!- Glimmer::UI::CustomWidget: ability to define any keyword as a custom widget - featured in Hello, Custom Widget!
- Glimmer::UI::CustomShell: ability to define any keyword as a custom shell (aka custom window) that opens in a new browser window (tab) automatically unless there is no shell open in the current browser window (tab) - featured in Hello, Custom Shell!
Layouts:
grid_layout: featured in Hello, Layout! / Hello, Custom Shell! / Hello, Computed! / Hello, Table! / Hello, Pop Up Context Menu! / Hello, Menu Bar! / Hello, List Single Selection! / Hello, List Multi Selection! / Contact Manager / Login / Tic Tac Toerow_layout: featured in Hello, Layout! / Hello, Radio Group! / Hello, Radio! / Hello, Group! / Hello, Date Time! / Hello, Combo! / Hello, Checkbox Group! / Hello, Checkbox! / Contact Managerfill_layout: featured in Hello, Layout! / Hello, Custom Widget!layout_data: featured in Hello, Layout! / Hello, Table! / Hello, Custom Shell! / Hello, Computed! / Tic Tac Toe / Contact Manager
Graphics/Style:
color: featured in Hello, Custom Widget! / Hello, Menu Bar!font: featured in Hello, Checkbox Group! / Hello, Checkbox! / Hello, Table! / Hello, Radio Group! / Hello, Radio! / Hello, Pop Up Context Menu! / Hello, Menu Bar! / Hello, Group! / Hello, Date Time! / Hello, Custom Widget! / Hello, Custom Shell! / Contact Manager / Tic Tac ToePointclass used in setting location on widgetsswtandSWTclass to set SWT styles on widgets - featured in Hello, Custom Shell! / Login / Contact Manager
Data-Binding/Observers:
bind: featured in Hello, Computed! / Hello, Combo! / Hello, Checkbox Group! / Hello, Checkbox! / Hello, Button! / Hello, Table! / Hello, Radio Group! / Hello, Radio! / Hello, List Single Selection! / Hello, List Multi Selection! / Hello, Group! / Hello, Date Time! / Hello, Custom Widget! / Hello, Custom Shell! / Login / Contact Manager / Tic Tac Toeobserve: featured in Hello, Table! / Tic Tac Toeon_widget_selected: featured in Hello, Combo! / Hello, Checkbox Group! / Hello, Checkbox! / Hello, Button! / Hello, Table! / Hello, Radio Group! / Hello, Radio! / Hello, Pop Up Context Menu! / Hello, Message Box! / Hello, Menu Bar! / Hello, List Single Selection! / Hello, List Multi Selection! / Hello, Group! / Contact Manager / Login / Tic Tac Toeon_modify_texton_key_pressed(and SWT aliason_swt_keydown) - featured in Login / Contact Manageron_key_released(and SWT aliason_swt_keyup)on_mouse_down(and SWT aliason_swt_mousedown)on_mouse_up(and SWT aliason_swt_mouseup) - featured in Hello, Custom Shell! / Contact Manager
Event loop:
display: featured in Tic Tac Toeasync_exec: featured in Hello, Custom Widget! / Hello, Custom Shell!sync_exec: executes a block on the event loop synchronously (usually from another thread)timer_exec: executes a block after a delay of time has passedauto_exec: executes a block on the event loop synchronously only when needed (when running from a thread other than GUI thread)
SWT Proxies
Glimmer follows Proxy Design Pattern by having Ruby proxy wrappers for all SWT objects:
Glimmer::SWT:WidgetProxywraps all descendants oforg.eclipse.swt.widgets.Widgetexcept the ones that have their own wrappers.Glimmer::SWT::ShellProxywrapsorg.eclipse.swt.widgets.ShellGlimmer::SWT:TabItemProxywrapsorg.eclipse.swt.widget.TabItem(also adds a composite to enable adding content under tab items directly in Glimmer)Glimmer::SWT:LayoutProxywraps all descendants oforg.eclipse.swt.widget.LayoutGlimmer::SWT:LayoutDataProxywraps all layout data objectsGlimmer::SWT:DisplayProxywrapsorg.eclipse.swt.widget.Display(manages displaying GUI)Glimmer::SWT:ColorProxywrapsorg.eclipse.swt.graphics.ColorGlimmer::SWT:FontProxywrapsorg.eclipse.swt.graphics.FontGlimmer::SWT::WidgetListenerProxywraps all widget listeners
These proxy objects have an API and provide some convenience methods, some of which are mentioned below.
swt_widget
Glimmer SWT proxies come with the instance method #swt_widget, which returns the actual SWT Widget object wrapped by the Glimmer widget proxy. It is useful in cases you'd like to do some custom SWT programming outside of Glimmer.
Shell Widget Proxy Methods
Shell widget proxy has extra methods specific to SWT Shell:
#open: Opens the shell, making it visible and active, and starting the SWT Event Loop (you may learn more about it here: https://help.eclipse.org/2019-12/nftopic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/widgets/Display.html). If shell was already open, but hidden, it makes the shell visible.#show: Alias for#open#hide: Hides a shell setting "visible" property to false#close: Closes the shell#center_within_display: Centers the shell within monitor it is in#start_event_loop: (happens as part of#open) Starts SWT Event Loop (you may learn more about it here: https://help.eclipse.org/2019-12/nftopic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/widgets/Display.html). This method is not needed except in rare circumstances where there is a need to start the SWT Event Loop before opening the shell.#visible?: Returns whether a shell is visible#opened_before?: Returns whether a shell has been opened at least once before (additionally implying the SWT Event Loop has been started already)#visible=: Setting to true opens/shows shell. Setting to false hides the shell.#layout: Lays out contained widgets using SWT'sShell#layoutmethod#pack: Packs contained widgets using SWT'sShell#packmethod#pack_same_size: Packs contained widgets without changing shell's size when widget sizes change
Widget Content Block
Glimmer allows re-opening any widget and adding properties or extra content after it has been constructed already by using the #content method.
Example (you may copy/paste in girb):
@shell = shell {
text "Application"
row_layout
@label1 = label {
text "Hello,"
}
}
@shell.content {
minimum_size 130, 130
label {
text "World!"
}
}
@label1.content {
foreground :red
}
@shell.open
Tab Folder API
Unlike basic SWT usage, tab_folder has the smart default of pre-initializing all tabs so that they are properly sized/filled so no delays occur when a user browses through them for the first time by selecting unselected tabs.
The Stock Ticker sample takes advantage of this to ensure all tabs are pre-initialized and filled with rendered data even before the user selects any of them.
That said, tab_folder can optionally receive a custom Glimmer SWT style named :initialize_tabs_on_select, which disables that behavior by going back to SWT's default of initializing tabs upon first selection (e.g. upon clicking with the mouse).
Shell Icon
To set the shell icon, simply set the image property under the shell widget. This shows up in the operating system toolbar and app-switcher (e.g. CMD+TAB) (and application window top-left corner in Windows)
Example:
shell {
# ...
image 'path/to/image.png'
# ...
}
Shell Icon Tip for Packaging on Windows
When setting shell icon for a packaged app, which has a JAR file at its core, you can reference the ico file that ships with the app by going one level up (e.g. '../AppName.ico')
Dialog
Dialog is a variation on Shell. It is basically a shell that is modal (blocks what's behind it) and belongs to another shell. It only has a close button.
Glimmer facilitates building dialogs by using the dialog keyword, which automatically adds the SWT.DIALOG_TRIM and SWT.APPLICATION_MODAL widget styles needed for a dialog.
Check out Hello, Dialog! sample to learn more.
message_box
The Glimmer DSL message_box keyword is similar to shell and dialog, but renders a modal dialog with a title text property, main body message property, and dismissal button(s) only (OK button by default or more options). It may also be opened via the #open method.
Example (you may copy/paste in girb):
include Glimmer
@shell = shell {
text 'Hello, Message Box!'
{
text 'Please Click To Win a Surprise'
{
(@shell) {
text 'Surprise'
"Congratulations!\n\nYou have won $1,000,000!"
}.open
}
}
}
@shell.open

It is also possible to use message_box even before instantiating the first shell (Glimmer builds a throwaway shell parent automatically for it):
Example (you may copy/paste in girb):
include Glimmer
{
text 'Greeting'
"Hello, World!"
}.open
Display
The SWT Display class is a singleton in Glimmer. It is used in SWT to represent your display device, allowing you to manage GUI globally
and access available monitors. Additionally, it is responsible for the SWT event loop, which runs on the first thread the Glimmer application starts on. In multi-threaded programming, Display provides the methods async_exec and sync_exec to enable enqueuing GUI changes asynchronously or synchronously from threads other than the main (first) thread since direct GUI changes are forbidden from other threads by design.
Display is automatically instantiated upon first instantiation of a shell widget.
Alternatively, for advanced use cases, a Display can be created explicitly with the Glimmer display keyword. When a shell is later declared, it
automatically uses the display created earlier without having to explicitly hook it.
@display = display {
cursor_location 300, 300
on_swt_keydown {
# ...
}
# ...
}
@shell = shell { # uses display created above
}
The benefit of instantiating an SWT Display explicitly is to set Properties or Observers. Although SWT Display is not technically a widget, it has similar APIs and DSL support.
Multi-Threading
JRuby supports truly parallel multi-threading since it relies on the JVM (Java Virtual Machine). As such, it enables development of highly-interactive desktop applications that can do background work while the user is interacting with the GUI. However, any code that interacts with the GUI from a thread other than the main (first) GUI thread must do so only through sync_exec (if it is standard synchronous code) or async_exec.
Most of the time, you simply get away with Ruby Threads and Mutexes.
Otherwise, if you need more advanced concurrency, Glimmer includes the concurrent-ruby gem, which supports many helpful concurrency techniques such as Thread Pools (used in the Mandelbrot Fractal sample).
One thing Glimmer DSL for SWT innovates over plain old SWT is not requiring developers to explicitly use Display.syncExec from threads other than the GUI threads.
Glimmer automatically detects if you're running in a different thread and uses Display.syncExec automatically using its own enhanced auto_exec
In any case, Glimmer still allows developers to manually use sync_exec, async_exec, timer_exec, and auto_exec when needed. M
async_exec
async_exec {} is a Glimmer DSL keyword in addition to being a method on display. It accepts a block and when invoked, adds the block to the end of a queue of GUI events scheduled to run on the SWT event loop, executing asynchronously.
Example (you may copy/paste in girb):
@shell = shell {
text 'Glimmer'
@label = label {
text 'Hello, World!'
}
}
Thread.new {
[:red, :dark_green, :blue].cycle { |color|
async_exec {
@label.content {
foreground color if @shell.visible?
}
}
sleep(1)
}
}
@shell.open
sync_exec
sync_exec {} is required by SWT when running GUI update from a thread other than the GUI thread. In Glimmer, it is automatically invoked for you so that you wouldn't have to worry about it. It works just like async_exec except it executes the block synchronously at the earliest opportunity possible, waiting for the block to be finished.
auto_exec
auto_exec(override_sync_exec:, override_async_exec) {} only executes code block with sync_exec when necessary (running from a thread other than the GUI thread). It is used automatically all over the Glimmer DSL for SWT codebase, so you wouldn't need it unless you grab a direct handle on swt_widget from a widget proxy.
timer_exec
timer_exec(delay_in_milliseconds) {} works just like async_exec except it executes the block after a delay has elapsed.
Menus
Glimmer DSL provides support for SWT Menu and MenuItem widgets.
There are 2 main types of menus in SWT:
- Menu Bar (shows up on top)
- Pop Up Context Menu (shows up when right-clicking a widget)
Underneath both types, there can be a 3rd menu type called Drop Down.
Glimmer provides special support for Drop Down menus as it automatically instantiates associated Cascade menu items and wires together with proper parenting, swt styles, and calling setMenu.
The ampersand symbol indicates the keyboard shortcut key for the menu item (e.g. '&Help' can be triggered on Windows by hitting ALT+H)
Example of a Menu Bar (you may copy/paste in girb):
include Glimmer
COLORS = [:white, :red, :yellow, :green, :blue, :magenta, :gray, :black]
shell {
grid_layout {
margin_width 0
margin_height 0
}
text 'Hello, Menu Bar!'
@label = label(:center) {
font height: 50
text 'Check Out The Menu Bar Above!'
}
{
{
text '&File'
{
text '&New'
accelerator :command, :N
{
{
text 'New'
'New file created.'
}.open
}
}
{
text '&Open...'
accelerator :command, :O
{
{
text 'Open'
'Opening File...'
}.open
}
}
{
text 'Open &Recent'
{
text 'File 1'
{
{
text 'File 1'
'File 1 Contents'
}.open
}
}
{
text 'File 2'
{
{
text 'File 2'
'File 2 Contents'
}.open
}
}
}
(:separator)
{
text 'E&xit'
{
exit(0)
}
}
}
{
text '&Edit'
{
text 'Cut'
accelerator :command, :X
}
{
text 'Copy'
accelerator :command, :C
}
{
text 'Paste'
accelerator :command, :V
}
}
{
text '&Options'
(:radio) {
text '&Enabled'
{
@select_one_menu.enabled = true
@select_multiple_menu.enabled = true
}
}
@select_one_menu = {
text '&Select One'
enabled false
(:radio) {
text 'Option 1'
}
(:radio) {
text 'Option 2'
}
(:radio) {
text 'Option 3'
}
}
@select_multiple_menu = {
text '&Select Multiple'
enabled false
(:check) {
text 'Option 4'
}
(:check) {
text 'Option 5'
}
(:check) {
text 'Option 6'
}
}
}
{
text '&Format'
{
text '&Background Color'
COLORS.each { |color_style|
(:radio) {
text color_style.to_s.split('_').map(&:capitalize).join(' ')
{
@label.background = color_style
}
}
}
}
{
text 'Foreground &Color'
COLORS.each { |color_style|
(:radio) {
text color_style.to_s.split('_').map(&:capitalize).join(' ')
{
@label.foreground = color_style
}
}
}
}
}
{
text '&View'
(:radio) {
text 'Small'
{
@label.font = {height: 25}
@label.parent.pack
}
}
(:radio) {
text 'Medium'
selection true
{
@label.font = {height: 50}
@label.parent.pack
}
}
(:radio) {
text 'Large'
{
@label.font = {height: 75}
@label.parent.pack
}
}
}
{
text '&Help'
{
text '&Manual'
accelerator :command, :shift, :M
{
{
text 'Manual'
'Manual Contents'
}.open
}
}
{
text '&Tutorial'
accelerator :command, :shift, :T
{
{
text 'Tutorial'
'Tutorial Contents'
}.open
}
}
(:separator)
{
text '&Report an Issue...'
{
{
text 'Report an Issue'
'Reporting an issue...'
}.open
}
}
}
}
}.open
Example of a Pop Up Context Menu (you may copy/paste in girb):
include Glimmer
shell {
grid_layout {
margin_width 0
margin_height 0
}
text 'Hello, Pop Up Context Menu!'
label {
text "Right-Click on the Text to\nPop Up a Context Menu"
font height: 50
{
{
text '&History'
{
text '&Recent'
{
text 'File 1'
{
{
text 'File 1'
'File 1 Contents'
}.open
}
}
{
text 'File 2'
{
{
text 'File 2'
'File 2 Contents'
}.open
}
}
}
{
text '&Archived'
{
text 'File 3'
{
{
text 'File 3'
'File 3 Contents'
}.open
}
}
{
text 'File 4'
{
{
text 'File 4'
'File 4 Contents'
}.open
}
}
}
}
}
}
}.open
Tray Item

The system tray allows showing icons for various apps that need to stay on for extended periods of time and provide quick access.
In Glimmer DSL for SWT, generating tray items is automated via the tray_item keyword, which can be nested under shell and then have a child menu underneath that pops up when the user clicks on its icon in the system tray.
Note how the shell was declared with the :on_top style (in addition to the default, which is :shell_trim) to ensure it opens above all apps when the "Show Application" menu item is selected.
Example code:
shell(:shell_trim, :on_top) { # make it always appear on top of everything
row_layout(:vertical) {
center true
}
text 'Hello, Tray Item!'
on_shell_closed do |event|
# do not perform event that closes app when shell is closed
event.doit = false
# body_root is the root shell
body_root.hide
self.show_application = false # updates Show Application checkbox menu item indirectly
end
tray_item {
tool_tip_text 'Glimmer'
image @image # could use an image path instead
{
{
text 'About'
do
{
text 'Glimmer - About'
'This is a Glimmer DSL for SWT Tray Item'
}.open
end
}
(:separator)
(:check) {
text 'Show Application'
selection <=> [self, :show_application]
do
# body_root is the root shell
if body_root.visible?
body_root.hide
else
body_root.show
end
end
}
(:separator)
{
text 'Exit'
{
exit(0)
}
}
}
# supported tray item listeners (you can try to add actions to them when needed)
# on_swt_Show {
# }
#
# on_swt_Hide {
# }
#
# on_widget_selected {
# }
#
# on_menu_detected {
# }
}
label(:center) {
text 'This is the application'
font height: 30
}
label {
text 'Click on the tray item (circles icon) to open its menu'
}
label {
text 'Uncheck Show Application to hide the app and recheck it to show the app'
}
}
Learn more at Hello, Tray Item!
ScrolledComposite
Glimmer provides smart defaults for the scrolled_composite widget by:
- Automatically setting the nested widget as its content (meaning use can just like a plain old
compositeto add scrolling) - Automatically setting the :h_scroll and :v_scroll SWT styles (can be set manually if only one of either :h_scroll or :v_scroll is desired )
- Automatically setting the expand horizontal and expand vertical SWT properties to
true
Sash Form Widget
sash_form is an SWT built-in custom widget that provides a resizable sash that splits a window area into two or more panes.
It can be customized with the weights attribute by setting initial weights to size the panes at first display.
One noteworthy thing about the Glimmer implementation is that, unlike behavior in SWT, it allows declaring weights before the content of the sash_form, thus providing more natural and convenient syntax (Glimmer automatically takes care of sending that declaration to SWT at the end of declaring sash_form content as per the SWT requirements)
Example (you may copy/paste in girb):
shell {
text 'Sash Form Example'
sash_form {
label {
text '(resize >>)'
background :dark_green
foreground :white
font height: 20
}
label {
text '(<< resize)'
background :red
foreground :white
font height: 20
}
weights 1, 2
}
}.open
You may check out a more full-fledged example in Hello, Sash Form!

Browser Widget

Glimmer supports the SWT Browser widget, which can load URLs or render HTML. It can even be instrumented with JavaScript when needed (though highly discouraged since it defeats the purpose of using Ruby except in very rare cases like leveraging a pre-existing web codebase in a desktop app).
Example loading a URL (you may copy/paste in girb):
shell {
minimum_size 1024, 860
maximum_size 2000, 1000
browser {
url 'http://brightonresort.com/about'
}
}.open
Example rendering HTML with JavaScript on document ready (you may copy/paste in girb provided you install and require glimmer-dsl-xml gem):
shell {
minimum_size 130, 130
@browser = browser {
text html {
head {
(name: "viewport", content: "width=device-width, initial-scale=2.0")
}
body {
h1 { "Hello, World!" }
}
}
on_completed { # on load of the page execute this JavaScript
@browser..execute("alert('Hello, World!');")
}
}
}.open
Learn more at the SWT Browser widget API.
Widget Styles
SWT widgets receive SWT styles in their constructor as per this guide:
https://wiki.eclipse.org/SWT_Widget_Style_Bits
Glimmer DSL facilitates that by passing symbols representing SWT constants as widget method arguments (i.e. inside widget () parentheses according to Glimmer Style Guide. See example below) in lower case version (e.g. SWT::MULTI becomes :multi).
These styles customize widget look, feel, and behavior.
Example:
# ...
list(:multi) { # SWT styles go inside ()
# ...
}
# ...
Passing :multi to list widget enables list element multi-selection.
# ...
composite(:border) { # SWT styles go inside ()
# ...
}
# ...
Passing :border to composite widget ensures it has a border.
When you need to pass in multiple SWT styles, simply separate by commas.
Example:
# ...
text(:center, :border) { # Multiple SWT styles separated by comma
# ...
}
# ...
Glimmer ships with SWT style smart defaults so you wouldn't have to set them yourself most of the time (albeit you can always override them):
text(:border)table(:border, :virtual, :full_selection)tree(:border, :virtual, :v_scroll, :h_scroll)spinner(:border)list(:border, :v_scroll)button(:push)
You may check out all available SWT styles here:
Explicit SWT Style Bit
When building a widget-related SWT object manually (e.g. GridData.new(...)), you are expected to use SWT::CONSTANT directly or BIT-OR a few SWT constants together like SWT::BORDER | SWT::V_SCROLL.
Glimmer facilitates that with swt keyword by allowing you to pass multiple styles as an argument array of symbols instead of dealing with BIT-OR.
Example:
style = swt(:border, :v_scroll)
Negative SWT Style Bits
In rare occasions, you might need to apply & with a negative (not) style bit to negate it from another style bit that includes it.
Glimmer facilitates that by declaring the negative style bit via postfixing a symbol with !.
Example:
style = swt(:shell_trim, :max!) # creates a shell trim style without the maximize button (negated)
Extra SWT Styles
Non-resizable Window
SWT Shell widget by default is resizable. To make it non-resizable, one must pass a complicated style bit concoction like swt(:shell_trim, :resize!, :max!).
Glimmer makes this easier by alternatively ing a :no_resize extra SWT style, added for convenience.
This makes declaring a non-resizable window as easy as:
shell(:no_resize) {
# ...
}
Fill Screen Window
SWT Shell can open and fill the screen with this style swt(:fill_screen). This makes it have the size of the display, thus filling the screen. Keep in mind that this is different from being maximized (which is a special window state, not just filling the screen).
Widget Properties
Widget properties such as text value, enablement, visibility, and layout details are set within the widget block using methods matching SWT widget property names in lower snakecase. You may refer to SWT widget guide for details on available widget properties:
Code examples:
# ...
label {
text "Hello, World!" # SWT properties go inside {} block
}
# ...
In the above example, the label widget text property was set to "Hello, World!".
# ...
{
enabled bind(@tic_tac_toe_board.box(row, column), :empty)
}
# ...
In the above example, the text widget enabled property was data-bound to #empty method on @tic_tac_toe_board.box(row, column) (learn more about data-binding below)
Color
Colors make up a subset of widget properties. SWT accepts color objects created with RGB (Red Green Blue) or RGBA (Red Green Blue Alpha). Glimmer supports constructing color objects using the rgb and rgba DSL keywords.
Example:
# ...
label {
background rgb(144, 240, 244)
foreground rgba(38, 92, 232, 255)
}
# ...
SWT also supports standard colors available as constants under the SWT namespace with the COLOR_ prefix (e.g. SWT::COLOR_BLUE)
Glimmer supports constructing colors for these constants as lowercase Ruby symbols (with or without color_ prefix) passed to color DSL keyword
Example:
# ...
label {
background color(:black)
foreground color(:yellow)
}
label {
background color(:color_white)
foreground color(:color_red)
}
# ...
You may check out all available standard colors in SWT over here (having COLOR_ prefix):
#swt_color
Glimmer color objects come with an instance method #swt_color that returns the actual SWT Color object wrapped by the Glimmer color object. It is useful in cases you'd like to do some custom SWT programming outside of Glimmer.
Example:
color(:black).swt_color # returns SWT Color object
Font
Fonts are represented in Glimmer as a hash of name, height, and style keys.
The style can be one (or more) of 3 values: :normal, :bold, and :italic
Example:
# ...
label {
font name: 'Arial', height: 36, style: :normal
}
# ...
Keys are optional, so some of them may be left off. When passing multiple styles, they are included in an array.
Example:
# ...
label {
font style: [:bold, :italic]
}
# ...
You may simply use the standalone font keyword without nesting in a parent if there is a need to build a Font object to use in manual SWT programming outside of widget font property setting.
Example:
@font = font(name: 'Arial', height: 36, style: :normal)
Image
The image keyword creates an instance of org.eclipse.swt.graphics.Image.
It is a graphics Image object (not a widget), but is used used in setting the image property on label and background_image on composite (and subclasses)
Glimmer recently included EXPERIMENTAL gif animation support for the background_image property on `composite' since SWT does not support animation by default. On Windows, it only works inside composites nested under standard shells, not ones that have the SWT styles :on_top or :no_trim
Learn more about images in general at this SWT Image guide: https://www.eclipse.org/articles/Article-SWT-images/graphics-resources.html
Image Options
Options may be passed in a hash at the end of image arguments:
width: width of imageheight: height of image
If only the width or height alone are specified, the other is calculated while maintaining the image aspect ratio.
Example:
label {
image 'someimage.png', width: 400, height: 300
}
Cursor
SWT widget cursor property represents the mouse cursor you see on the screen when you hover over that widget.
The Display class provides a way to obtain standard system cursors matching of the SWT style constants starting with prefix CURSOR_ (e.g. SWT::CURSOR_HELP shows a question mark mouse cursor)
Glimmer provides an easier way to obtain and set cursor property on a widget by simply mentioning the SWT style constant as an abbreviated symbol excluding the "CURSOR_" suffix.
Example:
shell {
minimum_size 128, 128
cursor :appstarting
}
This sets the shell cursor to that of SWT::CURSOR_APPSTARTING
Layouts
Glimmer lays widgets out visually using SWT layouts, which can only be set on composite widget and subclasses.
The most common SWT layouts are:
FillLayout: lays widgets out in equal proportion horizontally or vertically with spacing/margin options. This is the default layout for shell (with:horizontaloption) in Glimmer.RowLayout: lays widgets out horizontally or vertically in varying proportions with advanced spacing/margin/justify optionsGridLayout: lays widgets out in a grid with advanced spacing/margin/alignment/indentation options. This is the default layout for composite in Glimmer. It is important to master.
In Glimmer DSL, just like widgets, layouts can be specified with lowercase underscored names followed by a block containing properties, also lowercase underscored names (e.g. RowLayout is row_layout).
Example:
# ...
composite {
row_layout {
wrap true
pack false
justify true
type :vertical
margin_left 1
margin_top 2
margin_right 3
margin_bottom 4
spacing 5
}
# ... widgets follow
}
# ...
If you data-bind any layout properties, when they change, the shell containing their widget re-packs its children (calls #pack method automatically) to ensure proper relayout of all widgets.
Alternatively, a layout may be constructed by following the SWT API for the layout object. For example, a RowLayout can be constructed by passing it an SWT style constant (Glimmer automatically accepts symbols (e.g. :horizontal) for SWT style arguments like SWT::HORIZONTAL.)
# ...
composite {
row_layout :horizontal
# ... widgets follow
}
# ...
Here is a more sophisticated example taken from hello_computed.rb sample:

shell {
text 'Hello, Computed!'
composite {
grid_layout {
num_columns 2
make_columns_equal_width true
horizontal_spacing 20
vertical_spacing 10
}
label {text 'First &Name: '}
text {
text bind(@contact, :first_name)
layout_data {
horizontal_alignment :fill
grab_excess_horizontal_space true
}
}
label {text '&Last Name: '}
text {
text bind(@contact, :last_name)
layout_data {
horizontal_alignment :fill
grab_excess_horizontal_space true
}
}
label {text '&Year of Birth: '}
text {
text bind(@contact, :year_of_birth)
layout_data {
horizontal_alignment :fill
grab_excess_horizontal_space true
}
}
label {text 'Name: '}
label {
text bind(@contact, :name, computed_by: [:first_name, :last_name])
layout_data {
horizontal_alignment :fill
grab_excess_horizontal_space true
}
}
label {text 'Age: '}
label {
text bind(@contact, :age, on_write: :to_i, computed_by: [:year_of_birth])
layout_data {
horizontal_alignment :fill
grab_excess_horizontal_space true
}
}
}
}.open
Check out the samples directory for more advanced examples of layouts in Glimmer.
Defaults:
Glimmer composites always come with grid_layout by default, but you can still specify explicitly if you'd like to set specific properties on it.
Glimmer shell always comes with fill_layout having :horizontal type.
This is a great guide for learning more about SWT layouts:
https://www.eclipse.org/articles/Article-Understanding-Layouts/Understanding-Layouts.htm
Also, for a reference, check the SWT API:
https://help.eclipse.org/2019-12/nftopic/org.eclipse.platform.doc.isv/reference/api/index.html
Layout Data
Layouts organize widgets following common rules for all widgets directly under a composite. But, what if a specific widget needs its own rules. That's where layout data comes into play.
By convention, SWT layouts expect widgets to set layout data with a class matching their class name with the word "Data" replacing "Layout":
GridLayouton a composite demandsGridDataon contained widgetsRowLayouton a composite demandsRowDataon contained widgets
Not all layouts support layout data to further customize widget layouts. For example, FillLayout supports no layout data.
Unlike widgets and layouts in Glimmer DSL, layout data is simply specified with layout_data keyword nested inside a widget block body, and followed by arguments and/or a block of its own properties (lowercase underscored names).
Glimmer automatically deduces layout data class name by convention as per rule above, with the assumption that the layout data class lives under the same exact Java package as the layout (one can set custom layout data that breaks convention if needed in rare cases. See code below for an example)
Glimmer also automatically accepts symbols (e.g. :fill) for SWT style arguments like SWT::FILL.
Examples:
# ...
composite {
row_layout :horizontal
label {
layout_data { # followed by properties
width 50
height 30
}
}
# ... more widgets follow
}
# ...
# ...
composite {
grid_layout 3, false # grid layout with 3 columns not of equal width
label {
# layout data followed by arguments passed to SWT GridData constructor
layout_data :fill, :end, true, false
}
}
# ...
# ...
composite {
grid_layout 3, false # grid layout with 3 columns not of equal width
label {
# layout data set explicitly via an object (helps in rare cases that break convention)
layout_data GridData.new(swt(:fill), swt(:end), true, false)
}
}
# ...
If you data-bind any layout data properties, when they change, the shell containing their widget re-packs its children (calls #pack method automatically) to ensure proper relayout of all widgets.
NOTE: Layout data must never be reused between widgets. Always specify or clone again for every widget.
This is a great guide for learning more about SWT layouts:
https://www.eclipse.org/articles/Article-Understanding-Layouts/Understanding-Layouts.htm
Also, for a reference, check the SWT API:
https://help.eclipse.org/2019-12/nftopic/org.eclipse.platform.doc.isv/reference/api/index.html
Canvas Shape DSL
(ALPHA FEATURE)
While other GUI toolkits only offer a way to draw graphics imperatively (e.g. fill_rectangle, draw_point, etc...), Glimmer DSL for SWT breaks away from the mold by enabling software engineers to draw graphics declaratively. Simply declare all the shapes you want to see with their attributes, like background/foreground colors, and Glimmer DSL for SWT takes care of the rest, painting graphics on a blank canvas widget or amending/decorating an existing widget. This is accomplished through the Canvas Shape DSL, a sub-DSL of the Glimmer GUI DSL, which makes it possible to draw graphics declaratively with very understandable and maintainable syntax. Still, for the rare cases where imperative logic is needed, Glimmer DSL for SWT supports imperative painting of graphics through direct usage of SWT.
canvas has the :double_buffered SWT style by default on platforms that need it (Windows & Linux) to ensure flicker-free rendering. If you need to disable it for whatever reason, just pass the :none SWT style instead (e.g. canvas(:none))
Shape keywords and their args (including defaults) are listed below (they basically match method names and arguments on org.eclipse.swt.graphics.GC minus the draw or fill prefix in downcase):
arc(x, y, width, height, startAngle, arcAngle, fill: false)arc is part of a circle within an oval area, denoted by start angle (degrees) and end angle (degrees)focus(x, y, width, height)this is just like rectangle but its foreground color is always that of the OS widget focus color (useful when capturing user interaction via a shape)image(image, x = 0, y = 0)sets image, which could be an org.eclipse.swt.graphics.Image object or just a file path stringimage(image, source_x, source_y, source_width, source_height, destination_x, destination_y, destination_width, destination_height)sets part of an image and scales it to fit destination dimensions on parent canvas/widgetline(x1, y1, x2, y2)lineoval(x, y, width, height, fill: false)oval if width does not match heigh and circle if width matches height. Can be optionally filled.point(x, y)pointpolygon(pointArray, fill: false)polygon consisting of points, which close automatically to form a shape that can be optionally filled (when points only form a line, it does not show up as filled)polyline(pointArray)polyline is just like a polygon, but it does not close up to form a shape, remaining open (unless the points close themselves by having the last point or an intermediate point match the first)rectangle(x, y, width, height, fill: false)standard rectangle, which can be optionally filledrectangle(x, y, width, height, arcWidth = 60, arcHeight = 60, fill: false, round: true)round rectangle, which can be optionally filled, and takes optional extra round angle argumentsrectangle(x, y, width, height, vertical = true, fill: true, gradient: true)gradient rectangle, which is always filled, and takes an optional extra argument to specify true for vertical gradient (default) and false for horizontal gradienttext(string, x, y, is_transparent = true)text with optional is_transparent to indicate if background is transparent (default is true)text(string, x, y, flags)text with optional flags (flag format isswt(comma_separated_flags)where flags can be:draw_delimiter(i.e. new lines),:draw_tab,:draw_mnemonic, and:draw_transparentas explained in GC API)
Shape keywords that can be filled with color can take a keyword argument fill: true (or filled: true). Defaults to false when not specified unless background is set with no foreground (or foreground is set with no background), in which case a smart default is applied.
Smart defaults can be applied to automatically infer gradient: true (rectangle with both foreground and background) and round: true (rectangle with more than 4 args, the extra args are numeric) as well.
Optionally, a shape keyword takes a block that can set any attributes from org.eclipse.swt.graphics.GC (methods starting with set), which enable setting the background for filling and foreground for drawing.
Here is a list of supported attributes nestable within a block under shapes:
advancedenables advanced graphics subsystem (boolean value). Typically gets enabled automatically when setting alpha, antialias, patterns, interpolation, clipping. Rendering sometimes differs between advanced and non-advanced mode for basic graphics too, so you could enable manually if you prefer its look even for basic graphics.alphasets transparency (integer between0and255)antialiasenables antialiasing (SWT style value of:default(or nil),:off(or false),:on(or true) whereby:defaultapplies OS default, which varies per OS)backgroundsets fill color for fillable shapes (standard color symbol (e.g.:red),rgb(red_integer, green_integer, blue_integer)color, or Color/ColorProxy object directly)background_patternsets fill gradient/image pattern for fillable shape background (takes the same arguments as the SWT Pattern class [e.g.background_pattern 2.3, 4.2, 5.4, 7.2, :red, :blue] / note: this feature isn't extensively tested yet)clippingclips area of painting (numeric values for(x, y, width, height))fill_rulesets filling rule (SWT style value of:fill_even_oddor:fill_winding)fontsets font (Hash of:name,:height, and:stylejust like standard widget font property, or Font/FontProxy object directly)foregroundsets draw color for drawable shapes (standard color symbol (e.g.:red),rgb(red_integer, green_integer, blue_integer)color, or Color/ColorProxy object directly)foreground_patternsets foreground gradient/image pattern for drawable shape lines (takes the same arguments as the SWT Pattern class [e.g.foreground_pattern 2.3, 4.2, 5.4, 7.2, :red, :blue] / note: this feature isn't extensively tested yet)interpolationsets the interpolation value (SWT style value of:default,:none,:low,:high)line_capsets line cap (SWT style value of:cap_flat,:cap_round, or:cap_square)line_dashline dash float values (automatically setsline_styleto SWT style value of:line_custom)line_joinline join style (SWT style value of:join_miter,:join_round, or:join_bevel)line_styleline join style (SWT style value of:solid,:dash,:dot,:dashdot,:dashdotdot, or:customwhile requiringline_dashattribute (or alternatively withline_prefix as per official SWT docs like:line_solidfor:solid)line_widthline width in integer (used in draw operations)text_anti_aliasenables text antialiasing (SWT style value of:default,:off,:onwhereby:defaultapplies OS default, which varies per OS)transformsets transform object using Canvas Transform DSL syntax
Keep in mind that ordering of shapes matters as it is followed in painting. For example, it is recommended you paint filled shapes first and then drawn ones.
Example (you may copy/paste in girb):
include Glimmer
# image object has to be declared outside the canvas and shell to avoid confusing with canvas image property
image_object = image(File.('./icons/scaffold_app.png'), width: 100)
shell {
text 'Canvas Example'
minimum_size 320, 400
canvas {
background :dark_yellow
rectangle(0, 0, 220, 400) {
background :dark_red
}
rectangle(50, 20, 300, 150, 30, 50, round: true) {
background :yellow
}
rectangle(150, 200, 100, 70, true, gradient: true) {
background :dark_red
foreground :yellow
}
text('Glimmer', 208, 83) {
font height: 25, style: :bold
}
rectangle(200, 80, 108, 36) {
foreground rgb(0, 0, 0)
line_width 3
}
image(image_object, 70, 50)
}
}.open
Screenshot:

If you specify the x and y coordinates as :default, nil, or leave them out, they get calculated automatically by centering the shape within its parent canvas.
If you specify the width and height parameters as :max, they get calculated from the parent's size, filling the parent maximally.
Note that you could shift a shape off its centered position within its parent canvas by setting x to [:default, x_delta] and y to [:default, y_delta]
The round and gradient options could be dropped since Glimmer DSL for SWT supports auto-inference of them based on shape parameters.
Example (you may copy/paste in girb):
include Glimmer
# image object has to be declared outside the canvas and shell to avoid confusing with canvas image property
image_object = image(File.('./icons/scaffold_app.png'), width: 100)
shell {
text 'Canvas Example'
minimum_size 320, 400
canvas {
background :dark_yellow
rectangle(0, 0, 220, 400) {
background :dark_red
}
rectangle(50, 20, 300, 150, 30, 50) {
background :yellow
}
rectangle(150, 200, 100, 70, true) {
background :dark_red
foreground :yellow
}
text('Glimmer', 208, 83) {
font height: 25, style: :bold
}
rectangle(200, 80, 108, 36) {
foreground rgb(0, 0, 0)
line_width 3
}
image(image_object, 70, 50)
}
}.open
Notice how the shape declaration parameters perfectly match the method parameters in the SWT org.eclipse.swt.graphics.GC API. This is useful for developers coming to Glimmer DSL for SWT from SWT.
Of course, Glimmer DSL for SWT still supports an alternative syntax that is more declarative and consistent with the rest of the Glimmer GUI DSL syntax. This syntax in fact offers the extra-benefit of data-binding for shape parameter values (meaning you could use bind(...) syntax with them instead of setting values directly)
Example (you may copy/paste in girb):
include Glimmer
# image object has to be declared outside the canvas and shell to avoid confusing with canvas image property
image_object = image(File.('./icons/scaffold_app.png'), width: 100)
shell {
text 'Canvas Example'
minimum_size 320, 400
canvas {
background :dark_yellow
rectangle {
x 0
y 0
width 220
height 400
background :dark_red
}
rectangle {
x 50
y 20
width 300
height 150
arc_width 30
arc_height 50
background :yellow
}
rectangle {
x 150
y 200
width 100
height 70
vertical true
background :dark_red
foreground :yellow
}
text {
string 'Glimmer'
x 208
y 83
font height: 25, style: :bold
}
rectangle {
x 200
y 80
width 108
height 36
foreground :black
line_width 3
}
image {
image image_object
x 70
y 50
}
}
}.open
Learn more at the Hello, Canvas! Sample.
If you ever have special needs or optimizations, you could always default to direct SWT painting via org.eclipse.swt.graphics.GC instead. Learn more at the SWT Graphics Guide and SWT Image Guide.
Example of manually doing the same things as in the previous example without relying on the declarative Glimmer Shape DSL (you may copy/paste in girb):
include Glimmer
image_object = image(File.('./icons/scaffold_app.png'), width: 100)
shell {
text 'Canvas Manual Example'
minimum_size 320, 400
canvas {
background :dark_yellow
on_paint_control { |paint_event|
gc = paint_event.gc
gc.background = color(:dark_red).swt_color
gc.fill_rectangle(0, 0, 220, 400)
gc.background = color(:yellow).swt_color
gc.fill_round_rectangle(50, 20, 300, 150, 30, 50)
gc.background = color(:dark_red).swt_color
gc.foreground = color(:yellow).swt_color
gc.fill_gradient_rectangle(150, 200, 100, 70, true)
gc.font = font(height: 25, style: :bold).swt_font
gc.foreground = color(:black).swt_color
gc.draw_text('Glimmer', 208, 83, true)
gc.foreground = rgb(0, 0, 0).swt_color
gc.line_width = 3
gc.draw_rectangle(200, 80, 108, 36)
gc.draw_image(image_object.swt_image, 70, 50)
}
}
}.open
Shapes inside a Shape
Shapes can be nested within each other. If you nest a shape within another, its coordinates are assumed to be relative to its parent.
As such, if you move the parent, it moves all its children with it.
If you specify the x and y coordinates as :default, nil, or leave them out, they get calculated automatically by centering the shape within its parent shape relatively.
If you specify the width and height parameters as :default, nil, or leave them out, they get calculated automatically from the shape's nested children shapes if any (e.g calculating the dimensions of a text from its extent according to its font size) or from the parent's size otherwise.
If you specify the width and height parameters as :max, they get calculated from the parent's size, filling the parent maximally.
Note that you could shift a shape off its centered position within its parent shape by setting x to [:default, x_delta] and y to [:default, y_delta]
Check Hello, Canvas! for an example that nests lines, points, a polyline, and an image within a drawn rectangle parent:
rectangle(205, 50, 88, 96) {
foreground :yellow
3.times { |n|
line(45, 70 + n*10, 65 + n*10, 30 + n*10) {
foreground :yellow
}
}
10.times {|n|
point(15 + n*5, 50 + n*5) {
foreground :yellow
}
}
polyline(45, 60, 55, 20, 65, 60, 85, 80, 45, 60)
image(@image_object, 0, 5)
}
Shapes inside a Widget
Keep in mind that the Shape DSL can be used inside any widget, not just canvas. Unlike shapes on a canvas, which are standalone graphics, when included in a widget, which already has its own look and feel, shapes are used as a decorative add-on that complements its look by getting painted on top of it. For example, shapes were used to decorate composite blocks in the Tetris sample to have a more bevel look. In summary, Shapes can be used in a hybrid approach (shapes inside a widget), not just standalone in a canvas.
Shapes inside an Image
You can build an image using the Canvas Shape DSL (including setting the icon of the application).
Example (you may copy/paste in girb):
include Glimmer
shell {
text 'Image Shape DSL Example'
label {
bevel_constant = 20
icon_block_size = 64
icon_bevel_size = icon_block_size.to_f / 25.to_f
icon_bevel_pixel_size = 0.16*icon_block_size.to_f
icon_size = 8
icon_pixel_size = icon_block_size * icon_size
image(icon_pixel_size, icon_pixel_size) {
icon_size.times { |row|
icon_size.times { |column|
colored = row >= 1 && column.between?(1, 6)
color = colored ? color([:white, :red, :blue, :green, :yellow, :magenta, :cyan, :dark_blue].sample) : color(:white)
x = column * icon_block_size
y = row * icon_block_size
rectangle(x, y, icon_block_size, icon_block_size) {
background color
}
polygon(x, y, x + icon_block_size, y, x + icon_block_size - icon_bevel_pixel_size, y + icon_bevel_pixel_size, x + icon_bevel_pixel_size, y + icon_bevel_pixel_size) {
background rgb(color.red + 4*bevel_constant, color.green + 4*bevel_constant, color.blue + 4*bevel_constant)
}
polygon(x + icon_block_size, y, x + icon_block_size - icon_bevel_pixel_size, y + icon_bevel_pixel_size, x + icon_block_size - icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_block_size, y + icon_block_size) {
background rgb(color.red - bevel_constant, color.green - bevel_constant, color.blue - bevel_constant)
}
polygon(x + icon_block_size, y + icon_block_size, x, y + icon_block_size, x + icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_block_size - icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size) {
background rgb(color.red - 2*bevel_constant, color.green - 2*bevel_constant, color.blue - 2*bevel_constant)
}
polygon(x, y, x, y + icon_block_size, x + icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_bevel_pixel_size, y + icon_bevel_pixel_size) {
background rgb(color.red - bevel_constant, color.green - bevel_constant, color.blue - bevel_constant)
}
}
}
}
}
}.open

Example setting the icon of the application (you may copy/paste in girb):
include Glimmer
shell {
text 'Image Shape DSL Example'
label {
text 'Image Shape DSL Example'
font height: 30
}
bevel_constant = 20
icon_block_size = 64
icon_bevel_size = icon_block_size.to_f / 25.to_f
icon_bevel_pixel_size = 0.16*icon_block_size.to_f
icon_size = 8
icon_pixel_size = icon_block_size * icon_size
image(icon_pixel_size, icon_pixel_size) {
icon_size.times { |row|
icon_size.times { |column|
colored = row >= 1 && column.between?(1, 6)
color = colored ? color([:white, :red, :blue, :green, :yellow, :magenta, :cyan, :dark_blue].sample) : color(:white)
x = column * icon_block_size
y = row * icon_block_size
rectangle(x, y, icon_block_size, icon_block_size) {
background color
}
polygon(x, y, x + icon_block_size, y, x + icon_block_size - icon_bevel_pixel_size, y + icon_bevel_pixel_size, x + icon_bevel_pixel_size, y + icon_bevel_pixel_size) {
background rgb(color.red + 4*bevel_constant, color.green + 4*bevel_constant, color.blue + 4*bevel_constant)
}
polygon(x + icon_block_size, y, x + icon_block_size - icon_bevel_pixel_size, y + icon_bevel_pixel_size, x + icon_block_size - icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_block_size, y + icon_block_size) {
background rgb(color.red - bevel_constant, color.green - bevel_constant, color.blue - bevel_constant)
}
polygon(x + icon_block_size, y + icon_block_size, x, y + icon_block_size, x + icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_block_size - icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size) {
background rgb(color.red - 2*bevel_constant, color.green - 2*bevel_constant, color.blue - 2*bevel_constant)
}
polygon(x, y, x, y + icon_block_size, x + icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_bevel_pixel_size, y + icon_bevel_pixel_size) {
background rgb(color.red - bevel_constant, color.green - bevel_constant, color.blue - bevel_constant)
}
}
}
}
}.open
![]()
Custom Shapes
Glimmer enables defining custom shape keywords, which represent shapes made up of other nested shapes.
Custom shapes expand a software engineer's Glimmer GUI DSL vocabulary in their app development to be able to represent
higher visual concepts like vehicles, scenes, and characters with a single keyword (e.g. car, beach, player),
which embody and abstract all the details relating to the visual concept. This enables orders of magnitudes in increased
productivity and readability/maintainability as a result.
Just like custom widgets, custom shapes can be defined in one of two ways:
- method-based custom shapes: Use the
shapekeyword as a parent to represent a shape composite (similar to widget composite) and include other shapes like rectangles, polygons, and lines within. - class-based custom shapes: Include the
Glimmer::UI::CustomShapesupermodule and define theoptions,body {}block, andbefore_body/after_bodyhooks (similar to how it is down with custom widgets)
Check out Hello, Shape! and Hello, Custom Shape! for examples of both approaches.
Canvas Shape API
These Canvas Shape API methods help with manipulating shapes upon user interaction, such as mouse clicking a specific shape.
They are implemented with the help of the highly robust Java built-in shape geometry algorithms.
WidgetProxy#shape_at_location(x, y): returns shape object at x, y location from a widget proxy like canvasShape#contain?(x, y): indicates if shape contains x, y pointShape#include?(x, y): indicates if shape includes x, y point on the edge if drawn or inside if filled (include uses contain for filled shapes)Shape#move_by(x_delta, y_delta): moves shape object at x, y locationShape#rotate(angle): rotates around center by an angle (not cumulative, reseting angle on every call)Shape#rotatation_angle: current rotation angle (according to use of rotate method)Shape#dispose: disposes of shape, removing it form its parent canvas, widget, or shapeShape#content {}: reopens a shape to add more content inside it using the Glimmer GUI DSL (e.g. Canvas Shape DSL) just likeWidgetProxy#content {}.Shape#size: calculated size for shape bounding box (e.g. a polygon with an irregular shape will have its bounding box width and height calculated)Shape#bounds: calculated bounds (x, y, width, height) for shape bounding box (e.g. a polygon with an irregular shape will have its bounding box top-left x, y, width and height calculated)Shape#width: static (including:default) or derived width for shape (including irregular geometric shapes like Polygon)Shape#height: static (including:default) or derived height for shape (including irregular geometric shapes like Polygon)Shape#default_width?: whether:defaultor[:default, delta]is set for static widthShape#default_height?: whether:defaultor[:default, delta]is set for static heightShape#max_width?: whether:maxor[:max, delta]is set for static widthShape#max_height?: whether:maxor[:max, delta]is set for static heightShape#calculated_width: calculated width for shape when set to :default to indicate it is sized by its children (e.g. in the case of containing text with a specific font size not knowing its width/height dimensions in advance)Shape#calculated_height: calculated height for shape when set to :default to indicate it is sized by its children (e.g. in the case of containing text with a specific font size not knowing its width/height dimensions in advance)Shape#x: top-left corner x position, static or:default(could be relative if shape is nested within another shape)Shape#y: top-left corner y position, static or:default(could be relative if shape is nested within another shape)Shape#absolute_x: absolute top-left corner x positionShape#absolute_y: absolute top-left corner y positionShape#default_x?: whether x is specified via:defaultstyle to indicate centering within parent (or[:default, offset])Shape#default_y?: calculated top-left corner y positionShape#calculated_x: calculated top-left corner x position when default/delta is set (i.e. centered within parent)Shape#calculated_y: calculated top-left corner y position when default/delta is set (i.e. centered within parent)Shape#center_x: center xShape#center_y: center yShape#x_end: right-most included x coordinateShape#y_end: bottom-most included y coordinate
Check Hello, Canvas! for an example.
Pixel Graphics
(Early Alpha Feature)
If you need to paint pixel graphics, use the optimized pixel keyword alternative to point, which takes foreground as a hash argument and bypasses the Glimmer DSL Engine chain of responsibility, thus rendering faster when having very large pixel counts.
Example (you may copy/paste in girb):
include Glimmer
shell {
minimum_size 250, 265
text 'Pixel Graphics Example'
canvas {
250.times {|y|
250.times {|x|
pixel(x, y, foreground: [y%255, x%255, (x+y)%255])
}
}
}
}.open
Result:
![]()
If you are strictly dealing with pixels (no other shapes), you could even avoid the pixel keyword altogether and just provide direct foreground colors by passing a block that receives x, y coordinates:
include Glimmer
shell {
minimum_size 250, 265
text 'Pixel Graphics Example'
canvas { |x, y|
[y%255, x%255, (x+y)%255]
}
}.open
Remember that you could always default to direct SWT painting via org.eclipse.swt.graphics.GC too for even faster performance when needed in rare circumstances. Learn more at the SWT Graphics Guide and SWT Image Guide.
Example of manually doing the same things as in the previous example without relying on the declarative Glimmer Shape DSL (you may copy/paste in girb):
include Glimmer
shell {
minimum_size 250, 265
text 'Pixel Graphics Example'
canvas {
on_paint_control { |paint_event|
gc = paint_event.gc
250.times {|y|
250.times {|x|
gc.foreground = Color.new(y%255, x%255, (x+y)%255)
gc.draw_point(x, y)
}
}
}
}
}.open
(the code could be optimized further if you are repeating colors by simply reusing Color objects instead of re-constructing them)
The only downside with the approach above is that it repaints all pixels on repaints to the window (e.g. during window resize). To get around that, we can rely on a technique called Image Double-Buffering. That is to buffer the graphics on an Image first and then set it on the Canvas so that resizes of the shell dont cause a repaint of all the pixels. Additionally, this gives us the added benefit of being able to use the image as a Shell icon via its image property.
Example (you may copy/paste in girb):
include Glimmer
@the_image = image(250, 250)
250.times {|y|
250.times {|x|
@the_image.gc.foreground = Color.new(y%255, x%255, (x+y)%255)
@the_image.gc.draw_point(x, y)
}
}
shell {
minimum_size 250, 265
text 'Pixel Graphics Example'
image @the_image
canvas {
image @the_image
}
}.open
If you need a transparent background for the image, replace the image construction line with the following:
@the_image = image(250, 250)
@the_image.image_data.alpha = 0
@the_image = image(@the_image.image_data)
That way, wherever you don't draw a point, you get transparency (seeing what is behind the image).
Alternatively, with a very minor performance penalty, Glimmer enables you to build the image pixel by pixel with a friendly Ruby syntax by passing a block that takes the x and y coordinates and returns a foreground color rgb array or Color/ColorProxy object.
include Glimmer
@the_image = image(250, 250) {|x, y|
[y%255, x%255, (x+y)%255]
}
shell {
minimum_size 250, 265
text 'Pixel Graphics Example'
image @the_image
canvas {
image @the_image
}
}.open
If you don't need a shell image (icon), you can nest the image directly under the canvas by passing in the top_level keyword to treat image as a top-level keyword (pretending it is built outside the shell).
include Glimmer
shell {
minimum_size 250, 265
text 'Pixel Graphics Example'
canvas {
image image(250, 250, top_level: true) {|x, y|
[y%255, x%255, (x+y)%255]
}
}
}.open
If you don't need a shell image (icon) and pixel performance is enough, you can automatically apply Image Double-Buffering with the :image_double_buffered SWT style (custom Glimmer style not available in SWT itself)
Example (you may copy/paste in girb):
include Glimmer
shell {
minimum_size 250, 265
text 'Pixel Graphics Example'
canvas(:image_double_buffered) {
250.times {|y|
250.times {|x|
pixel(x, y, foreground: [y%255, x%255, (x+y)%255])
}
}
}
}.open
Of course, you could also take advantage of the pixel-less terser syntax:
include Glimmer
shell {
minimum_size 250, 265
text 'Pixel Graphics Example'
canvas(:image_double_buffered) { |x, y|
[y%255, x%255, (x+y)%255]
}
}.open
As they say, there are many ways to skin a cat! This is in line with the Ruby way of providing more ways than one. Pick and choose the right tool for the job just like true software engineers.
Canvas Path DSL
(ALPHA FEATURE)
Unlike common imperative GUI graphing toolkits, Glimmer enables declarative rendering of paths with the new Canvas Path DSL (Early Alpha) via the new path { } keyword and by nesting one of the following path segment keywords underneath:
point(x1, y1): renders a Point (Dot) as part of a path.line(x1, y1, x2=nil, y2=nil): renders a Line as part of a path. If you drop x2, y2, it joins to the previous point automatically. You may repeat for a series of lines forming a curve.quad(x1, y1, x2, y2, x3=nil, y3=nil): renders a Quadratic Bezier Curve. If you drop x3 and y3, it joins to the previous point automatically.cubic(x1, y1, x2, y2, x3, y3, x4=nil, y4=nil): renders a Cubic Bezier Curve. If you drop x4 and y4, it joins to the previous point automatically.
Example:
include Glimmer
shell {
text 'Canvas Path Example'
minimum_size 300, 300
canvas {
path {
foreground :black
250.times {|n|
cubic(n + n%30, n+ n%50, 40, 40, 70, 70, n + 20 + n%30, n%30*-1 * n%50)
}
}
}
}.open

Learn more at the Hello, Canvas Path! and Stock Ticker samples.

Canvas Path API
Every path segment object (mixing in Glimmer::SWT::Custom::PathSegment like path, point, line, quad, and cubic) has the following API methods:
#path: indicates which path the segment is part of.#path_root: indicates the root path the segment is part of.#dispose: disposes a path segment from its path#content {}: reopens a path to add more segments inside it using the Glimmer GUI DSL (e.g. Canvas Path DSL) just likeWidgetProxy#content {}.#first_path_segment?: indicates if the path segment is the first in the path
Canvas Transform DSL
(ALPHA FEATURE)
The transform DSL builds org.eclipse.swt.graphics.Transform objects with a nice declarative syntax.
transform keyword builds a Transform object. It optionally takes the transformation matrix elements: (m11, m12, m21, m22, dx, dy)
The first 2 values represent the 1st row, the second 2 values represent the 2nd row, and the last 2 values represent translation on the x and y axes
Additionally, Transform operation keywords may be nested within the transform keyword to set its properties:
identityresets transform to identity (no transformation)invert(alias:inversion) inverts a transformmultiply(&transform_properties_block)(alias:multiplication) multiplies by another transform (takes a block representing properties of another transform, no need for using the word transform again)rotate(angle)(alias:rotation) rotates by angle degreesscale(x, y)scales a shape (stretch)shear(x, y)shear effecttranslate(x, y)(alias:translation) translate x and y coordinates (move)elements(m11, m12, m21, m22, dx, dy)resets all values of the transform matrix (first 2 values represent the 1st row, second 2 values represent the 2nd row, the last 2 values represent translation on x and y axes)
Also, setting transform to nil after a previous transform has been set is like calling identity. It resets the transform.
Example (you may copy/paste in girb):
include Glimmer
shell {
text 'Canvas Transform Example'
minimum_size 330, 352
canvas { |canvas_proxy|
background :white
text('glimmer', 0, 0) {
foreground :red
transform {
translate 220, 100
scale 2.5, 2.5
rotate 90
}
}
text('glimmer', 0, 0) {
foreground :dark_green
transform {
translate 0, 0
shear 2, 3
scale 2, 2
}
}
text('glimmer', 0, 0) {
foreground :blue
transform {
translate 0, 220
scale 3, 3
}
}
}
}.open

Top-Level Transform Fluent Interface
When using a transform at the top-level (outside of shell), you get a fluent interface to faciliate manual construction and use.
Example:
include Glimmer # make sure it is included in your class/module before using the fluent interface
transform(1, 1, 4, 2, 2, 4).
multiply(1, 2, 3, 4,3,4).
scale(1, 2, 3, 4, 5, 6).
rotate(45).
scale(2, 4).
invert.
shear(2, 4).
translate(3, 7)
Learn more at the Hello, Canvas Transform! Sample.
Canvas Animation DSL
(EXPERIMENTAL EARLY ALPHA FEATURE)
(note: this is a very new feature of Glimmer. It may change a bit while getting battle tested. As always, you could default to basic SWT usage if needed.)
Glimmer provides built-in support for animations via a declarative Animation DSL, another sub-DSL of the Glimmer GUI DSL.
Animations take advantage of multi-threading, with Glimmer DSL for SWT automatically running each animation in its own independent thread of execution while updating the GUI asynchronously.
Multiple simultaneous animations are supported per canvas (or widget) parent.
canvas has the :double_buffered SWT style by default on platforms other than the Mac to ensure flicker-free rendering (Mac does not need it). If you need to disable it for whatever reason, just pass the :none SWT style instead (e.g. canvas(:none))
This example says it all (it moves a tiny red square across a blue background) (you may copy/paste in girb):
include Glimmer
shell {
text 'Canvas Animation Example'
minimum_size 400, 400
canvas {
animation {
every 0.1
frame { |index|
background rgb(index%100, index%100 + 100, index%55 + 200)
rectangle(index, index, 20, 20) {
background :red
}
}
}
}
}.open
Screenshot:

Keywords:
animationdeclares an animation under a canvas, which renders frames using a frame block indefinitely or finitely depending on (cycle_count/frame_count) propertieseveryspecifies delay in seconds between every two frame renders (an alternative tofps, cannot use together)fps(alias:frame_rate) specifies frame rate as frames per second (an alternative toevery, cannot use together)frame {|index, cycle_var| }a block that can contain Shape DSL syntax that is rendered dynamically with variables calculated on the flycyclean optional property that takes an array to cycle into a second variable for theframeblockcycle_countan optional cycle count limit after which the animation stops (finishes)frame_countan optional frame count limit after which the animation stops (finishes)duration_limitan optional duration limit in seconds after which the animation stops (finishes)started/started?a boolean indicating if the animation is started right away or stopped waiting for manual startup via#startmethodfinished/finished?a boolean indicating if the animation finished (for finite animations only)
API of Animation Object (returned from animation keyword):
#startstarts an animation that is indefinite or has never been started before (i.e. havingstarted: falseoption). Otherwise, resumes a stopped animation that has not been completed.#stopstops animation. Maintains progress whenframe_count,cycle_count, orduration_limitare set and haven't finished. That way, if#startis called, animation resumes from where it stopped exactly to completion.#restartrestarts animation, restarting progress offrame_count,cycle_count, andduration_limitif set.#started?returns whether animation started#stopped?returns whether animation stopped#indefinite?(aliasinfinite?) returns true if animation does not haveframe_count,cycle_count, orduration_limit#finite?returns true if animation hasframe_count,cycle_count(withcycle), orduration_limit#frame_count_limited?returns true ifframe_countis specified#cycle_enabled?returns true ifcycleis specified#cycle_limited?returns true ifcycle_countis specified#duration_limited?returns true ifduration_limitis specified
Learn more at the Hello, Canvas Animation! and Hello, Canvas Animation Data Binding! samples.
If there is anything missing you would like added to the Glimmer Animation DSL that you saw available in the SWT APIs, you may report an issue or implement yourself and contribute via a Pull Request.
Animation via Data-Binding
Animation could be alternatively implemented without the animation keyword through a loop that invokes model methods inside sync_exec {} (or async_exec {}), which indirectly cause updates to the GUI via data-binding.
The Glimmer Tetris sample provides a good example of that.
Data-Binding
Data-binding is done with bind command following widget property to bind and taking model and bindable attribute as arguments.
General Examples
text bind(contact, :first_name)
This example binds the text property of a widget like label to the first name of a contact model.
text bind(contact, 'address.street')
This example binds the text property of a widget like label to the nested street of
the address of a contact. This is called nested property data binding.
text bind(contact, 'address.street', on_read: :upcase, on_write: :downcase)
This example adds on the one above it by specifying converters on read and write of the model property, like in the case of a text widget. The text widget will then displays the street upper case and the model will store it lower case. When specifying converters, read and write operations must be symmetric (to avoid an infinite update loop between the widget and the model since the widget checks first if value changed before updating)
text bind(contact, 'address.street', sync_exec: true)
(BETA FEATURE)
This example forces GUI updates via sync_exec assuming they are coming from another thread (different from the GUI thread)
text bind(contact, 'address.street', async_exec: true)
(BETA FEATURE)
This example forces GUI updates via async_exec assuming they are coming from another thread (different from the GUI thread)
text bind(contact, 'address.street', on_read: lambda { |s| s[0..10] })
This example also specifies a converter on read of the model property, but via a lambda, which truncates the street to 10 characters only. Note that the read and write operations are assymetric. This is fine in the case of formatting data for a read-only widget like label
text bind(contact, 'address.street') { |s| s[0..10] }
This is a block shortcut version of the syntax above it. It facilitates formatting model data for read-only widgets since it's a very common view concern. It also saves the developer from having to create a separate formatter/presenter for the model when the view can be an active view that handles common simple formatting operations directly.
text bind(contact, 'address.street', read_only: true)
This is read-ohly data-binding. It doesn't update contact.address.street when widget text property is changed.
text bind(contact, 'addresses[1].street')
This example binds the text property of a widget like label to the nested indexed address street of a contact. This is called nested indexed property data binding.
text bind(contact, :age, computed_by: :date_of_birth)
This example demonstrates computed value data binding whereby the value of age depends on changes to date_of_birth.
text bind(contact, :name, computed_by: [:first_name, :last_name])
This example demonstrates computed value data binding whereby the value of name depends on changes to both first_name and last_name.
text bind(contact, 'profiles[0].name', computed_by: ['profiles[0].first_name', 'profiles[0].last_name'])
This example demonstrates nested indexed computed value data binding whereby the value of profiles[0].name depends on changes to both nested profiles[0].first_name and profiles[0].last_name.
Example from samples/hello/hello_combo.rb sample (you may copy/paste in girb):
Shine
(BETA FEATURE)
The new Shine syntax for View/Model Attribute Mapping allows data-binding visually with simple arrow operators in Ruby.
Use <=> [model, attribute, options] for bidirectional (two-way) data-binding instead of bind(model, attribute, options).
Use <= [model, attribute, options] for unidirectional (one-way) data-binding instead of bind(model, attribute, read_only: true, more_options)
One thing special with the table widget is that <=, which makes data-binding unidirectional, stops the table from supporting automatic sorting by default since that involves modifying the model collection ordering (albeit not the content).
To enable automatic sorting in a table, but still not permit edit actions to the data itself, you simply use <=> for bidirectional data-binding, but without passing the :editable style to the table.
Examples:
text <=> [@contact, :first_name]
text <=> [@contact, :last_name]
text <= [@contact, :name, computed_by: [:first_name, :last_name]]
Given that Shine is new, if you encounter any issues, you can use bind instead.
Check out sample code for more examples of Shine syntax in action, such as Hello, Computed!.
Combo
The combo widget provides a dropdown of options. By default, it also allows typing in a new option. To disable that behavior, you may use with the :read_only SWT style.
When data-binding a combo widget, Glimmer can automatically deduce available options from data-bound model by convention: {attribute_name}_options method.


class Person
attr_accessor :country, :country_options
def initialize
self.=["", "Canada", "US", "Mexico"]
self.country = "Canada"
end
def reset_country
self.country = "Canada"
end
end
class HelloCombo
include Glimmer
def launch
person = Person.new
shell {
composite {
combo(:read_only) {
selection bind(person, :country)
}
{
text "Reset"
do
person.reset_country
end
}
}
}.open
end
end
HelloCombo.new.launch
combo widget is data-bound to the country of a person. Note that it expects the person object to have the :country attribute and :country_options attribute containing all available countries (aka options). Glimmer reads these attributes by convention.
List
Example from samples/hello/hello_list_single_selection.rb sample:

shell {
composite {
list {
selection bind(person, :country)
}
{
text "Reset"
do
person.reset_country
end
}
}
}.open
list widget is also data-bound to the country of a person similarly to the combo widget. Not much difference here (the rest of the code not shown is the same).
Nonetheless, in the next example, a multi-selection list is declared instead allowing data-binding of multiple selection values to the bindable attribute on the model.
Example from samples/hello/hello_list_multi_selection.rb sample (you may copy/paste in girb):

class Person
attr_accessor :provinces, :provinces_options
def initialize
self.=[
"",
"Quebec",
"Ontario",
"Manitoba",
"Saskatchewan",
"Alberta",
"British Columbia",
"Nova Skotia",
"Newfoundland"
]
self.provinces = ["Quebec", "Manitoba", "Alberta"]
end
def reset_provinces
self.provinces = ["Quebec", "Manitoba", "Alberta"]
end
end
class HelloListMultiSelection
include Glimmer
def launch
person = Person.new
shell {
composite {
list(:multi) {
selection bind(person, :provinces)
}
{
text "Reset"
do
person.reset_provinces
end
}
}
}.open
end
end
HelloListMultiSelection.new.launch
The Glimmer code is not much different from above except for passing the :multi style to the list widget. However, the model code behind the scenes is quite different as it is a provinces array bindable to the selection of multiple values on a list widget. provinces_options contains all available province values just as expected by a single selection list and combo.
Note that in all the data-binding examples above, there was also an observer attached to the button widget to trigger an action on the model, which in turn triggers a data-binding update on the list or combo. Observers will be discussed in more details in the next section.
You may learn more about Glimmer's data-binding syntax by reading the code under the samples directory.
Table
The SWT Tree widget renders a multi-column data table, such as a contact listing or a sales report.
To data-bind a Table, you need the main model, the collection property, and the text display attribute for each table column.
This involves using the bind keyword mentioned above in addition to a special column_properties keyword that takes the table column text attribute methods.
It assumes you have defined the table columns via table_column widget.
Example:
shell {
@table = table {
table_column {
text "Name"
width 120
}
table_column {
text "Age"
width 120
}
table_column {
text "Adult"
width 120
}
items bind(group, :people), column_properties(:name, :age, :adult)
selection bind(group, :selected_person)
on_mouse_up { |event|
@table.edit_table_item(event.table_item, event.column_index)
}
}
}
The code above includes two data-bindings:
- Table
items, which first bind to the model collection property (group.people), and then maps each column property (name, age, adult) for displaying each table item column. - Table
selection, which binds the single table item selected by the user to the attribute denoted by thebindkeyword (or binds multiple table items selected for a table with:multiSWT style) - The
on_mouse_upevent handler invokes@table.edit_table_item(event.table_item, event.column_index)to start edit mode on the clicked table item cell, and then saves or cancel depending on whether the user hits ENTER or ESC once done editing (or focus-out after either making a change or not making any changes.)
Additionally, Table items data-binding automatically stores each node model unto the SWT TableItem object via setData method. This enables things like searchability.
The table widget in Glimmer is represented by a subclass of WidgetProxy called TableProxy.
TableProxy includes a search method that takes a block to look for a table item.
Example:
found_array = @table.search { |table_item| table_item.getData == company.owner }
This finds a person. The array is a Java array. This enables easy passing of it to SWT Table#setSelection method, which expects a Java array of TableItem objects.
To edit a table, you must invoke TableProxy#edit_selected_table_item(column_index, before_write: nil, after_write: nil, after_cancel: nil) or TableProxy#edit_table_item(table_item, column_index, before_write: nil, after_write: nil, after_cancel: nil).
This automatically leverages the SWT TableEditor custom class behind the scenes, displaying a text widget to the user to change the selected or
passed table item text into something else.
It automatically persists the change to items data-bound model on ENTER/FOCUS-OUT or cancels on ESC/NO-CHANGE.
Table Selection
Table Selection data-binding is simply done via the selection property.
selection bind(group, :selected_person)
If it's a multi-selection table (table(:multi)), then the data-bound model property oughta be a collection.
selection bind(group, :selected_people)
Table Editing
Glimmer provides a custom SWT style for table editing called :editable to obviate the need for an on_mouse_up listener.
For example, the code above could be simplified as:
shell {
@table = table(:editable) {
table_column {
text "Name"
width 120
}
table_column {
text "Age"
width 120
}
table_column {
text "Adult"
width 120
}
items bind(group, :people), column_properties(:name, :age, :adult)
selection bind(group, :selected_person)
}
}
Additionally, Glimmer supports the idea of custom editors or no editor per column.
Example:
shell {
@table = table(:editable) {
table_column {
text "Name"
width 120
}
table_column {
text "Age"
width 120
editor :spinner
}
table_column {
text "Adult"
width 120
editor :checkbox
}
items bind(group, :people), column_properties(:name, :age, :adult)
selection bind(group, :selected_person)
}
}
The example above uses a spinner widget editor for the age column since it's an Integer and
a checkbox widget (button(:check)) editor for the adult column since it's a Boolean
Here are all the supported types of table editors:
text: expects aStringpropertycombo: expects aStringproperty accompanied by a matchingproperty_optionsproperty by convention to provide items to present in thecombocheckbox: expects aBooleanpropertyradio: expects aBooleanpropertyspinner: expects anIntegerpropertydate: expects aDateTimepropertydate_drop_down: expects aDateTimepropertytime: expects aDateTimeproperty
An editor may also take additional arguments (SWT styles such as :long for the date field) that are passed to the editor widget, as well as hash options to customize the property being used for editing (e.g. property: :raw_name for a :formatted_name field) in case it differs from the property used to display the data in the table.
Example:
shell {
@table = table(:editable) {
table_column {
text "Date of Birth"
width 120
editor :date_drop_down, property: :date_time
}
table_column {
text "Industry"
width 120
# assume there is a `Person#industry_options` property method on the model to provide items to the `combo`
editor :combo, :read_only # passes :ready_only SWT style to `combo` widget
}
items bind(group, :people), column_properties(:formatted_date, :industry)
selection bind(group, :selected_person)
}
}
Check out Hello, Table! for an actual example including table editors.
Are We There Yet? is an actual production Glimmer application that takes full advantage of table capabilities, storing model data in a database via ActiveRecord. As such, it's an excellent demonstration of how to use Glimmer DSL for SWT with a database.
Table Sorting
Glimmer automatically adds sorting support to the SWT Table widget.
Check out the Contact Manager sample for an example. You may click on any column and it will sort by ascending order first and descending if you click again.
Glimmer automatic table sorting supports String, Integer, and Float columns out of the box as well as any column data that is comparable.
In cases where data is nil, depending on the data-type, it is automatically converted to Float with to_f, Integer with to_i, or String with to_s.
Should you have a special data type that could not be compared automatically, Glimmer s the following 3 alternatives for custom sorting:
sort_property: this may be set to an alternative property to the one data-bound to the table column. For example, a table column called 'adult', which returnstrueorfalsemay be sorted withsort_property :dobinstead. This also support multi-property (aka multi-column) sorting (e.g.sort_property :dob, :name).sort_by(&block): this works just like RubyEnumerablesort_by. The block receives the table column data as argument.sort(&comparator): this works just like RubyEnumerablesort. The comparator block receives two objects from the table column data.
These alternatives could be used inside table_column for column-clicked sorting or in the table body directly to set the initial default sort.
You may also set additional_sort_properties on the parent table widget to have secondary sorting applied. For example, if you set additional_sort_properties :name, :project_name, then whenever you sort by :name, it additionally sorts by :project_name afterwards, and vice versa. This only works for columns that either have no custom sort set or have a sort_property with one property only (but no sort or sort_by block)
Example:
# ...
table {
table_column {
text 'Task'
width 120
}
table_column {
text 'Project'
width 120
}
table_column {
text 'Duration (hours)'
width 120
sort_property :duration_in_hours
}
table_column {
text 'Priority'
width 120
sort_by { |value| ['High', 'Medium', 'Low'].index(value) }
}
table_column {
text 'Start Date'
width 120
sort { |d1, d2| d1.to_date <=> d2.to_date }
}
additional_sort_properties :project_name, :duration_in_hours, :name
items bind(Task, :list), column_properties(:name, :project_name, :duration, :priority, :start_date)
# ...
}
# ...
Here is an explanation of the example above:
- Task and Project table columns are data-bound to the
:nameand:project_nameproperties and sorted through them automatically - Task Duration table column is data-bound to the
:durationproperty, but sorted via the:duration_in_hoursproperty instead - Task Priority table column has a custom sort_by block
- Task Start Date table column has a custom sort comparator block
- Additional (secondary) sort properties are applied when sorting by Task, Project, or Duration in the order specified
bind(model, :property, read_only_sort: true) could be used with items to make sorting not propagate sorting changes to model.
Tree
The SWT Tree widget visualizes a tree data-structure, such as an employment or composition hierarchy.
To data-bind a Tree, you need the root model, the children querying method, and the text display attribute on each child.
This involves using the bind keyword mentioned above in addition to a special tree_properties keyword that takes the children and text attribute methods.
Example:
shell {
@tree = tree {
items bind(company, :owner), tree_properties(children: :coworkers, text: :name)
selection bind(company, :selected_coworker)
}
}
The code above includes two data-bindings:
- Tree
items, which first bind to the root node (company.owner), and then dig down viacoworkerschildrenmethod, using thenametextattribute for displaying each tree item. - Tree
selection, which binds the single tree item selected by the user to the attribute denoted by thebindkeyword
Additionally, Tree items data-binding automatically stores each node model unto the SWT TreeItem object via setData method. This enables things like searchability.
The tree widget in Glimmer is represented by a subclass of WidgetProxy called TreeProxy.
TreeProxy includes a depth_first_search method that takes a block to look for a tree item.
Example:
found_array = @tree.depth_first_search { |tree_item| tree_item.getData == company.owner }
This finds the root node. The array is a Java array. This enables easy passing of it to SWT Tree#setSelection method, which expects a Java array of TreeItem objects.
To edit a tree, you must invoke TreeProxy#edit_selected_tree_item or TreeProxy#edit_tree_item. This automatically leverages the SWT TreeEditor custom class behind the scenes, displaying
a text widget to the user to change the selected or passed tree item text into something else. It automatically persists the change to items data-bound model on ENTER/FOCUS-OUT or cancels on ESC/NO-CHANGE.
Learn more at the Hello, Tree! and Gladiator samples.
DateTime
date_time represents the SWT DateTime widget.
Glimmer s the following alias keywords for it for convenience:
date:date_time(:date)date_drop_down:date_time(:date, :drop_down)time:date_time(:time)calendar:date_time(:calendar)
You can data-bind any of these properties:
date_time bind(model, :property): produces a Ruby DateTime objectdate bind(model, :property): produces a Ruby Date objecttime bind(model, :property): produces a Ruby Time objectyear bind(model, :property): produces an integermonth bind(model, :property): produces an integerday bind(model, :property): produces an integerhours bind(model, :property): produces an integerminutes bind(model, :property): produces an integerseconds bind(model, :property): produces an integer
Learn more at the Hello, Date Time! sample.
If you need a better widget with the ability to customize the date format pattern, check out the Nebula CDateTime Glimmer Custom Widget
Observer
Glimmer comes with the Observer mixin module, which is used internally for data-binding, but can also be used externally for custom use of the Observer Pattern. It is hidden when observing widgets, and used explicitly when observing models. In bidirectional data-binding, Observer is automatically unregistered from models once a widget is disposed to avoid memory leaks and worrying about managing them yourself.
Observing Widgets
Glimmer supports observing widgets with two main types of events:
on_{swt-listener-method-name}: where swt-listener-method-name is replaced with the lowercase underscored event method name on an SWT listener class (e.g.on_verify_textfororg.eclipse.swt.events.VerifyListener#verifyText).on_swt_{swt-event-constant}: where swt-event-constant is replaced with anorg.eclipse.swt.SWTevent constant (e.g.on_swt_showforSWT.Showto observe when widget becomes visible)
Additionally, there are two more types of events:
- SWT
displaysupports global listeners called filters that run on any widget. They are hooked viaon_swt_{swt-event-constant} - SWT
displaysupports Mac application menu item observers (on_aboutandon_preferences), which you can read about under Miscellaneous.
Number 1 is more commonly used in SWT applications, so make it your starting point. Number 2 covers events not found in number 1, so look into it if you don't find an SWT listener you need in number 1.
Regarding number 1, to figure out what the available events for an SWT widget are, check out all of its add***Listener API methods, and then open the listener class argument to check its "event methods".
For example, if you look at the Button SWT API:
https://help.eclipse.org/2019-12/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fswt%2Fbrowser%2FBrowser.html
It has addSelectionListener. Additionally, under its Control super class, it has addControlListener, addDragDetectListener, addFocusListener, addGestureListener, addHelpListener, addKeyListener, addMenuDetectListener, addMouseListener, addMouseMoveListener, addMouseTrackListener, addMouseWheelListener, addPaintListener, addTouchListener, and addTraverseListener
Suppose, we select addSelectionListener, which is responsible for what happens when a user selects a button (clicks it). Then, open its argument SelectionListener SWT API, and you find the event (instance) methods: widgetDefaultSelected and widgetSelected. Let's select the second one, which is what gets invoked when a button is clicked.
Now, Glimmer simplifies the process of hooking into that listener (observer) by neither requiring you to call the addSelectionListener method nor requiring you to implement/extend the SelectionListener API.
Instead, simply add a on_widget_selected followed by a Ruby block containing the logic to perform. Glimmer figures out the rest.
Let's revisit the Tic Tac Toe example shown near the beginning of the page:
shell {
text "Tic-Tac-Toe"
minimum_size 150, 178
composite {
grid_layout 3, true
(1..3).each { |row|
(1..3).each { |column|
{
layout_data :fill, :fill, true, true
text bind(@tic_tac_toe_board[row, column], :sign)
enabled bind(@tic_tac_toe_board[row, column], :empty)
{
@tic_tac_toe_board.mark(row, column)
}
}
}
}
}
}
Note that every Tic Tac Toe grid cell has its text and enabled properties data-bound to the sign and empty attributes on the TicTacToe::Board model respectively.
Next however, each of these Tic Tac Toe grid cells, which are clickable buttons, have an on_widget_selected observer, which once triggered, marks the cell on the TicTacToe::Board to make a move.
Regarding number 2, you can figure out all available events by looking at the org.eclipse.swt.SWT API:
Example (you may copy/paste in girb):
SWT.Show - hooks a listener for showing a widget (using on_swt_show in Glimmer)
SWT.Hide - hooks a listener for hiding a widget (using on_swt_hide in Glimmer)
shell {
@button1 = {
text "Show 2nd Button"
visible true
on_swt_show {
@button2..setVisible(false)
}
{
@button2..setVisible(true)
}
}
@button2 = {
text "Show 1st Button"
visible false
on_swt_show {
@button1..setVisible(false)
}
{
@button1..setVisible(true)
}
}
}.open
Gotcha: SWT.Resize event needs to be hooked using on_swt_Resize because org.eclipse.swt.SWT has 2 constants for resize: RESIZE and Resize, so it cannot infer the right one automatically from the underscored version on_swt_resize
Alternative Syntax
Instead of declaring a widget observer using on_*** syntax inside a widget content block, you may also do so after the widget declaration by invoking directly on the widget object.
Example (you may copy/paste in girb):
@shell = shell {
label {
text "Hello, World!"
}
}
@shell.on_shell_iconified {
@shell.close
}
@shell.open
The shell declared above has been modified so that the minimize button works just like the close button. Once you minimize the shell (iconify it), it closes.
The alternative syntax can be helpful if you prefer to separate Glimmer observer declarations from Glimmer GUI declarations, or would like to add observers dynamically based on some logic later on.
Observing Models
Glimmer DSL includes an observe keyword used to register an observer by passing in the observable and the property(ies) to observe, and then specifying in a block what happens on notification.
class TicTacToe
include Glimmer
def initialize
# ...
observe(@tic_tac_toe_board, :game_status) { |game_status|
if game_status == Board::WIN
if game_status == Board::DRAW
}
end
# ...
end
Observers can be a good mechanism for displaying dialog messages in Glimmer (using SWT's MessageBox class).
Look at samples/elaborate/tictactoe/tic_tac_toe.rb for more details starting with the code included below.
class TicTacToe
include Glimmer
include Observer
def initialize
# ...
observe(@tic_tac_toe_board, :game_status) { |game_status|
if game_status == Board::WIN
if game_status == Board::DRAW
}
end
def
("Player #{@tic_tac_toe_board.winning_sign} has won!")
end
def
("Draw!")
end
def ()
(@shell) {
text 'Game Over'
}.open
@tic_tac_toe_board.reset
end
# ...
end
Custom Widgets
Custom widgets are brand new Glimmer DSL keywords that represent aggregates of existing widgets (e.g. address_form), customized existing widgets (e.g. greeting_label), or brand new widgets (e.g. oscilloscope)
You can find out about published Glimmer Custom Widgets by running the glimmer list:gems:customwidget command
Glimmer supports three ways of creating custom widgets with minimal code:
- Method-based Custom Widgets (for single-view-internal reuse): Extract a method containing Glimmer DSL widget syntax. Useful for quickly eliminating redundant code within a single view.
- Class-based Custom Widgets (for multiple-view-external reuse): Create a class that includes the
Glimmer::UI::CustomWidgetmodule and Glimmer DSL widget syntax in abody {}block. This will automatically extend Glimmer's DSL syntax with an underscored lowercase keyword matching the class name by convention. Useful in making a custom widget available in many views.
Approach #1 is a casual Ruby-based approach. Approach #2 is the official Glimmer approach. Typically, when referring to Custom Widgets, we are talking about Class-based Custom Widgets.
A developer might start with approach #1 to eliminate duplication in a view and later upgrade it to approach #2 when needing to export a custom widget to make it available in many views.
Class-based Custom Widgets a number of benefits over method-based custom widgets, such as built-in support for passing SWT style, nested block of extra widgets and properties, and before_body/after_body hooks.
Simple Example
Method-Based Custom Widget Example
(you may copy/paste in girb)
Definition and usage in the same file:
def red_label(label_text)
label {
text label_text
background :red
}
end
shell {
red_label('Red Label')
}.open
Class-Based Custom Widget Example
Simply create a new class that includes Glimmer::UI::CustomWidget and put Glimmer DSL code in its #body block (its return value is stored in #body_root attribute). Glimmer will then automatically recognize this class by convention when it encounters a keyword matching the class name converted to underscored lowercase (and namespace double-colons :: replaced with double-underscores __)
(you may copy/paste in girb)
Definition:
class RedLabel
include Glimmer::UI::CustomWidget
body {
label(swt_style) {
background :red
}
}
end
Usage:
shell {
red_label(:center) {
text 'Red Label'
foreground :green
}
}.open
As you can see, RedLabel became the Glimmer DSL keyword red_label and worked just like a standard label by taking in SWT style and nested properties. As such, it is a first-class citizen of the Glimmer GUI DSL.
Custom Widget Lifecycle Hooks
You may execute code before or after evaluating the body with these lifecycle hooks:
before_body: takes a block that executes in the custom widget instance scope before callingbody. Useful for initializing variables to later use inbodyafter_body: takes a block that executes in the custom widget instance scope after callingbody. Useful for setting up observers on widgets built inbody(set in instance variables) and linking to other shells.
Lifecycle Hooks Example
(you may copy/paste in girb)
Definition:
module Red
class Composite
include Glimmer::UI::CustomWidget
before_body {
@color = :red
}
body {
composite(swt_style) {
background @color
}
}
end
end
Usage:
shell {
red__composite {
label {
foreground :white
text 'This is showing inside a Red Composite'
}
}
}.open
Notice how Red::Composite became red__composite with double-underscore, which is how Glimmer Custom Widgets signify namespaces by convention. Additionally, the before_body lifecycle hook was utilized to set a @color variable and use inside the body.
Keep in mind that namespaces are not needed to be specified if the Custom Widget class has a unique name, not clashing with a basic SWT widget or another custom widget name.
Custom Widget API
Custom Widgets have the following attributes available to call from inside the #body method:
#parent: Glimmer object parenting custom widget#swt_style: SWT style integer. Can be useful if you want to allow consumers to customize a widget inside the custom widget body#options: a hash of options passed in parentheses when declaring a custom widget (useful for passing in model data) (e.g.calendar(events: events)). Custom widget class can declare option names (array) with::optionsclass method as shown below, which generates attribute accessors for every option (not to be confused with#optionsinstance method for retrieving options hash containing names & values)#content: nested block underneath custom widget. It will be automatically called at the end of processing the custom widget body. Alternatively, the custom widget body may callcontent.callat the place where the content is needed to show up as shown in the following example.#body_root: top-most (root) widget returned from#bodymethod.#swt_widget: actual SWT widget forbody_root
Additionally, custom widgets can call the following class methods:
::options(*option_names): declares a list of options by taking an option name array (symbols/strings). This generates option attribute accessors (e.g.options :orientation, :bg_colorgenerates#orientation,#orientation=(v),#bg_color, and#bg_color=(v)attribute accessors)::option(option_name, default: nil): declares a single option taking option name and default value as arguments (also generates attribute accessors just like::options)
Content/Options Example
(you may copy/paste in girb)
Definition:
class Sandwich
include Glimmer::UI::CustomWidget
:orientation, :bg_color
option :fg_color, default: :black
body {
composite(swt_style) { # gets custom widget style
fill_layout orientation # using orientation option
background bg_color # using container_background option
label {
text 'SANDWICH TOP'
}
content.call # this is where content block is called
label {
text 'SANDWICH BOTTOM'
}
}
}
end
Usage:
shell {
sandwich(:no_focus, orientation: :vertical, bg_color: :red) {
label {
background :green
text 'SANDWICH CONTENT'
}
}
}.open
Notice how :no_focus was the swt_style value, followed by the options hash {orientation: :horizontal, bg_color: :white}, and finally the content block containing the label with 'SANDWICH CONTENT'
Custom Widget Gotchas
Beware of defining a custom attribute that is a common SWT widget property name.
For example, if you define text= and text methods to accept a custom text and then later you write this body:
# ...
def text
# ...
end
def text=(value)
# ...
end
body {
composite {
label {
text "Hello"
}
label {
text "World"
}
}
}
# ...
The text method invoked in the custom widget body will call the one you defined above it. To avoid this gotcha, simply name the text property above something else, like custom_text.
Built-In Custom Widgets
Checkbox Group Custom Widget
checkbox_group (or alias check_group) is a Glimmer built-in custom widget that displays a list of checkbox buttons (button(:check)) based on its items property.
checkbox_group consists of a root composite (with grid_layout 1, false by default) that holds nested checkbox (button(:check)) widgets.
The selection property determines which checkbox buttons are checked. It expects an Array of String objects
The selection_indices property determines which checkbox button indices are checked. It expects an Array of index Integer objects that are zero-based.
The checkboxes property returns the list of nested checkbox widgets.
When data-binding selection, the model property should have a matching property with _options suffix (e.g. activities_options for activities) to provide an Array of String objects for checkbox buttons.
You may see an example at the Hello, Checkbox Group! sample.

Radio Group Custom Widget
radio_group is a Glimmer built-in custom widget that displays a list of radio buttons (button(:radio)) based on its items property, which expects an Array of String objects.
radio_group consists of a root composite (with grid_layout 1, false by default) that holds nested radio widgets.
The selection property determines which radio button is selected. It expects a String
The selection_index property determines which radio button index is selected. It expects an index integer that is zero-based.
The radios property returns the list of nested radio widgets.
When data-binding selection, the model property should have a matching property with _options suffix (e.g. country_options for country) to provide text for radio buttons.
This custom widget is used in the Glimmer Meta-Sample (The Sample of Samples):

Glimmer Meta-Sample Code Example:
# ...
radio_group { |radio_group_proxy|
row_layout(:vertical) {
fill true
}
selection bind(sample_directory, :selected_sample_name)
font height: 24
}
# ...
You may see another example at the Hello, Radio Group! sample.
Code Text Custom Widget
code_text is a Glimmer built-in custom widget that displays syntax highlighted Ruby code in a customized SWT StyledText widget.
It is used in the Glimmer Meta-Sample (The Sample of Samples):

Glimmer Meta-Sample Code Example:
# ...
@code_text = code_text {
text bind(SampleDirectory, 'selected_sample.code', read_only: true)
editable bind(SampleDirectory, 'selected_sample.editable')
}
# ...
To use, simply use code_text in place of the text or styled_text widget. If you set its text value to Ruby code, it automatically styles it with syntax highlighting.
Options
lines
(default: false)
Shows line numbers when set to true.
If set to a hash like {width: 4}, it sets the initial width of the line numbers lane in character count (default: 4)
Keep in mind that if the text grows and required a wider line numbers area, it grows automatically regardless of initial width.
theme
(default: 'glimmer')
(BETA FEATURE)
Changes syntax color highlighting theme. Can be one of the following:
- glimmer
- github
- pastie
language
(default: 'ruby')
(BETA FEATURE)
Sets the code language, which can be one of the following rouge gem supported languages:
- abap
- actionscript
- ada
- apache
- apex
- apiblueprint
- apple_script
- armasm
- augeas
- awk
- batchfile
- bbcbasic
- bibtex
- biml
- bpf
- brainf*ck
- brightscript
- bsl
- c
- ceylon
- cfscript
- clean
- clojure
- cmake
- cmhg
- coffeescript
- common_lisp
- conf
- console
- coq
- cpp
- crystal
- csharp
- css
- csvs
- cuda
- cypher
- cython
- d
- dart
- datastudio
- diff
- digdag
- docker
- dot
- ecl
- eex
- eiffel
- elixir
- elm
- epp
- erb
- erlang
- escape
- factor
- fortran
- freefem
- fsharp
- gdscript
- ghc_cmm
- ghc_core
- gherkin
- glsl
- go
- gradle
- graphql
- groovy
- hack
- haml
- handlebars
- haskell
- haxe
- hcl
- hlsl
- hocon
- hql
- html
- http
- hylang
- idlang
- igorpro
- ini
- io
- irb
- isbl
- j
- janet
- java
- javascript
- jinja
- jsl
- json
- json_doc
- jsonnet
- jsp
- jsx
- julia
- kotlin
- lasso
- liquid
- literate_coffeescript
- literate_haskell
- livescript
- llvm
- lua
- lustre
- lutin
- m68k
- magik
- make
- markdown
- mason
- mathematica
- matlab
- minizinc
- moonscript
- mosel
- msgtrans
- mxml
- nasm
- nesasm
- nginx
- nim
- nix
- objective_c
- objective_cpp
- ocaml
- ocl
- openedge
- opentype_feature_file
- pascal
- perl
- php
- plain_text
- plist
- pony
- postscript
- powershell
- praat
- prolog
- prometheus
- properties
- protobuf
- puppet
- python
- q
- qml
- r
- racket
- reasonml
- rego
- rescript
- robot_framework
- ruby
- rust
- sas
- sass
- scala
- scheme
- scss
- sed
- shell
- sieve
- slice
- slim
- smalltalk
- smarty
- sml
- solidity
- sparql
- sqf
- sql
- ssh
- supercollider
- swift
- systemd
- tap
- tcl
- terraform
- tex
- toml
- tsx
- ttcn3
- tulip
- turtle
- twig
- typescript
- vala
- varnish
- vb
- velocity
- verilog
- vhdl
- viml
- vue
- wollok
- xml
- xojo
- xpath
- xquery
- yaml
- yang
- zig
default_behavior (default: true)
(BETA FEATURE)
This adds some default keyboard shortcuts:
- CMD+A (CTRL+A on Windows/Linux) to select all
- CTRL+A on Mac to jump to beginning of line
- CTRL+E on Mac to jump to end of line
- Attempts to add proper indentation upon adding a new line when hitting ENTER (currently supporting Ruby only)
If you prefer it to be vanilla with no default key event listeners, then pass the default_behavior: false option.
Learn more at Hello, Code Text!
Video Custom Custom Widget
Glimmer supports a video custom widget not in SWT, which was originally a Glimmer built-in custom widget, but has been later extracted into its own Ruby gem.
Simply install the glimmer-cw-video gem.
Custom Widget Final Notes
This Eclipse guide for how to write custom SWT widgets is also applicable to Glimmer Custom Widgets written in Ruby. I recommend reading it: https://www.eclipse.org/articles/Article-Writing%20Your%20Own%20Widget/Writing%20Your%20Own%20Widget.htm
Also, you may check out Hello, Custom Widget! for another example.
Custom Shells
Custom shells are a kind of custom widgets that have shells only as the body root. They can be self-contained applications that may be opened and hidden/closed independently of the main app.
They may also be chained in a wizard fashion.
You can find out about published Glimmer Custom Shells by running the glimmer list:gems:customshell command
Example (you may copy/paste in girb):
class WizardStep
include Glimmer::UI::CustomShell
:number, :step_count
before_body {
@title = "Step #{number}"
}
body {
shell {
text "Wizard - #{@title}"
minimum_size 200, 100
fill_layout :vertical
label(:center) {
text @title
font height: 30
}
if number < step_count
{
text "Go To Next Step"
{
body_root.hide
}
}
end
}
}
end
shell { |app_shell|
text "Wizard"
minimum_size 200, 100
@current_step_number = 1
@wizard_steps = 5.times.map { |n|
wizard_step(number: n+1, step_count: 5) {
on_swt_hide {
if @current_step_number < 5
@current_step_number += 1
app_shell.hide
@wizard_steps[@current_step_number - 1].open
end
}
}
}
{
text "Start"
font height: 40
{
app_shell.hide
@wizard_steps[@current_step_number - 1].open
}
}
}.open
If you use a Custom Shell as the top-level app shell, you may invoke the class method .launch instead to avoid building an app class yourself or including Glimmer into the top-level namespace (e.g. Tetris.launch instead of include Glimmer; tetris.open)
You may check out Hello, Custom Shell! for another example.
Drag and Drop
Glimmer s Drag and Drop support, thanks to SWT and Glimmer's lightweight DSL syntax.
You may learn more about SWT Drag and Drop support over here: https://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html
To get started, simply follow these steps:
- On the drag source widget, add
on_drag_set_dataDragSourceListener event handler block at minimum (you may also addon_drag_startandon_drag_finishedif needed) - Set
event.datato transfer via drag and drop inside theon_drag_set_dataevent handler block (defaults totransfertype of:text, as in a Ruby String) - On the drop target widget, add
on_dropDropTargetListener event handler block at minimum (you may also addon_drag_enter[must setevent.detailif added],on_drag_over,on_drag_leave,on_drag_operation_changedandon_drop_acceptif needed) - Read
event.dataand consume it (e.g. change widget text) inside theon_dropevent handler block.
Example (taken from samples/hello/hello_drag_and_drop.rb / you may copy/paste in girb):
class Location
attr_accessor :country
def
%w[USA Canada Mexico Columbia UK Australia Germany Italy Spain]
end
end
@location = Location.new
include Glimmer
shell {
text 'Hello, Drag and Drop!'
list {
selection bind(@location, :country)
on_drag_set_data { |event|
list = event..getControl
event.data = list.getSelection.first
}
}
label(:center) {
text 'Drag a country here!'
font height: 20
on_drop { |event|
event..getControl.setText(event.data)
}
}
}.open

Optional steps:
- Set a
transferproperty (defaults to:text). Values may be: :text (default), :html :image, :rtf, :url, and :file, or an array of multiple values. Thetransferproperty will automatically convert your option into a Transfer object as per the SWT API. - Specify
drag_source_styleoperation (may be: :drop_copy (default), :drop_link, :drop_move, :drop_none, or an array of multiple operations) - Specify
drag_source_effect(Check DragSourceEffect SWT API for details) - Specify
drop_target_styleoperation (may be: :drop_copy (default), :drop_link, :drop_move, :drop_none, or an array of multiple operations) - Specify
drop_target_effect(Check DropTargetEffect SWT API for details) - Set drag operation in
event.detail(e.g. DND::DROP_COPY) insideon_drag_enter
Miscellaneous
Multi-DSL Support
Glimmer is a DSL engine that supports multiple DSLs (Domain Specific Languages):
- SWT: Glimmer DSL for SWT (Desktop GUI)
- Opal: Glimmer DSL for Opal (Web GUI Adapter for Desktop Apps)
- XML: Glimmer DSL for XML (& HTML) - Useful with SWT Browser Widget
- CSS: Glimmer DSL for CSS (Cascading Style Sheets) - Useful with SWT Browser Widget
Glimmer automatically recognizes top-level keywords in each DSL and activates DSL accordingly. Glimmer allows mixing DSLs, which comes in handy when doing things like using the SWT Browser widget with XML and CSS. Once done processing a nested DSL top-level keyword, Glimmer switches back to the prior DSL automatically.
SWT
The SWT DSL was already covered in detail. However, for the sake of mixing DSLs, you need to know that the SWT DSL has the following top-level keywords:
shelldisplaycolorobserveasync_execsync_exec
Opal
Full instructions are found in the Opal DSL page.
The Opal DSL is simply a web GUI adapter for desktop apps written in Glimmer. As such, it supports all the DSL keywords of the SWT DSL and shares the same top-level keywords.
XML
Simply start with html keyword and add HTML inside its block using Glimmer DSL syntax.
Once done, you may call to_s, to_xml, or to_html to get the formatted HTML output.
Here are all the Glimmer XML DSL top-level keywords:
htmltag: enables custom tag creation for exceptional cases by passing tag name as '_name' attributename_space: enables namespacing html tags
Element properties are typically passed as a key/value hash (e.g. section(id: 'main', class: 'accordion')) . However, for properties like "selected" or "checked", you must leave value nil or otherwise pass in front of the hash (e.g. input(:checked, type: 'checkbox') )
Example (basic HTML / you may copy/paste in girb):
@xml = html {
head {
(name: "viewport", content: "width=device-width, initial-scale=2.0")
}
body {
h1 { "Hello, World!" }
}
}
puts @xml
Output:
<html><head><meta name="viewport" content="width=device-width, initial-scale=2.0" /></head><body><h1>Hello, World!</h1></body></html>
Example (explicit XML tag / you may copy/paste in girb):
puts tag(:_name => "DOCUMENT")
Output:
<DOCUMENT/>
Example (XML namespaces using name_space keyword / you may copy/paste in girb):
@xml = name_space(:w3c) {
html(:id => "thesis", :class => "document") {
body(:id => "main") {
}
}
}
puts @xml
Output:
<w3c:html id="thesis" class="document"><w3c:body id="main"></w3c:body></w3c:html>
Example (XML namespaces using dot operator / you may copy/paste in girb):
@xml = tag(:_name => "DOCUMENT") {
document.body(document.id => "main") {
}
}
puts @xml
Output:
<DOCUMENT><document:body document:id="main"></document:body></DOCUMENT>
CSS
Simply start with css keyword and add stylesheet rule sets inside its block using Glimmer DSL syntax.
Once done, you may call to_s or to_css to get the formatted CSS output.
css is the only top-level keyword in the Glimmer CSS DSL
Selectors may be specified by s keyword or HTML element keyword directly (e.g. body)
Rule property values may be specified by pv keyword or underscored property name directly (e.g. font_size)
Example (you may copy/paste in girb):
@css = css {
body {
font_size '1.1em'
pv 'background', 'white'
}
s('body > h1') {
background_color :red
pv 'font-size', '2em'
}
}
puts @css
Listing / Enabling / Disabling DSLs
Glimmer provides a number of methods on Glimmer::DSL::Engine to configure DSL support or inquire about it:
Glimmer::DSL::Engine.dsls: Lists available Glimmer DSLsGlimmer::DSL::Engine.disable_dsl(dsl_name): Disables a specific DSL. Useful when there is no need for certain DSLs in a certain application.- `Glimmer::DSL::Engine.disabled_dsls': Lists disabled DSLs
Glimmer::DSL::Engine.enable_dsl(dsl_name): Re-enables disabled DSLGlimmer::DSL::Engine.enabled_dsls=(dsl_names): Disables all DSLs except the ones specified.
Application Menu Items (About/Preferences)
Mac applications always have About and Preferences menu items. Glimmer provides widget observer hooks for them on the display:
on_about: executes code when user selects App Name -> Abouton_preferences: executes code when user selects App Name -> Preferences or hits 'CMD+,' on the Mac
Example (you may copy/paste in girb):
class Example
def initialize
display {
on_about {
(@shell_proxy) {
text 'About'
'About Application'
}.open
}
on_preferences {
preferences_dialog = dialog {
text 'Preferences'
row_layout {
type :vertical
margin_left 15
margin_top 15
margin_right 15
margin_bottom 15
}
label {
text 'Check one of these options:'
}
(:radio) {
text 'Option 1'
}
(:radio) {
text 'Option 2'
}
}
preferences_dialog.open
}
}
@shell_proxy = shell {
text 'Application Menu Items'
fill_layout {
margin_width 15
margin_height 15
}
label {
text 'Application Menu Items'
font height: 30
}
}
@shell_proxy.open
end
end
Example.new
App Name and Version
Application name (shows up on the Mac in top menu bar) and version may be specified upon packaging by specifying "-Bmac.CFBundleName" and "-Bmac.CFBundleVersion" options.
Still, if you would like proper application name to show up on the Mac top menu bar during development, you may do so by invoking the SWT Display.app_name= method before any Display object has been instantiated (i.e. before any Glimmer widget like shell has been declared).
Example (you may copy/paste in girb):
Display.app_name = 'Glimmer Demo'
shell(:no_resize) {
text "Glimmer"
label {
text "Hello, World!"
}
}.open
Also, you may invoke Display.app_version = '1.0.0' if needed for OS app version identification reasons during development, replacing '1.0.0' with your application version.
Performance Profiling
JRuby comes with built-in support for performance profiling via the --profile option (with some code shown below), which can be accepted by the glimmer command too:
glimmer --profile path_to_glimmer_app.rb
Additionally, add this code to monitor Glimmer app performance around its launch method:
require 'jruby/profiler'
profile_data = JRuby::Profiler.profile do
SomeGlimmerApp.launch
end
profile_printer = JRuby::Profiler::HtmlProfilePrinter.new(profile_data)
ps = java.io.PrintStream.new(STDOUT.to_outputstream)
When monitoring app startup time performance, make sure to add a hook to the top-level shell on_swt_show event that exits the app as soon as the shell shows up to end performance profiling and get the results.
Example:
shell {
# some code
on_swt_show {
exit(0)
}
}
You may run glimmer with the --profile.graph instead for a more detailed output.
Learn more at the JRuby Performance Profile WIKI page.
SWT Browser Style Options
The browser widget can use a particular desktop browser by setting the SWT Style to:
:webkit: use the Webkit browser engine:chromium: use the Chromium browser engine
Example using the Chromium browser (you may copy/paste in girb):
shell {
minimum_size 1024, 860
browser(:chromium) {
url 'http://brightonresort.com/about'
}
}.open
This relies on Glimmer's Multi-DSL Support for building the HTML text using Glimmer XML DSL.
License
Copyright (c) 2007-2021 - Andy Maleh.

