Glimmer DSL for Tk 0.0.26
MRI Ruby Desktop Development GUI Library
Contributors Wanted! (Submit a Glimmer App Sample to Get Started)
Glimmer DSL for Tk enables desktop development with Glimmer in Ruby.
Tcl/Tk has recently improved by gaining native looking themed widgets on Mac, Windows, and Linux in Tk version 8.5.
Additionally, Ruby 3.0 Ractor (formerly known as Guilds) supports truly parallel multi-threading, making both MRI and Tk finally viable for support in Glimmer (Ruby Desktop Development GUI Library) as an alternative to JRuby on SWT.
The trade-off is that while SWT provides a plethora of high quality reusable widgets for the Enterprise (such as Nebula), Tk enables very fast app startup time and a small memory footprint via MRI Ruby.
Glimmer DSL for Tk aims to provide a DSL similar to the Glimmer DSL for SWT to enable more productive desktop development in Ruby with:
- Declarative DSL syntax that visually maps to the GUI widget hierarchy
- Convention over configuration via smart defaults and automation of low-level details
- Requiring the least amount of syntax possible to build GUI
- Bidirectional Data-Binding to declaratively wire and automatically synchronize GUI with Business Models
- Custom Widget support
- Scaffolding for new custom widgets, apps, and gems
- Native-Executable packaging on Mac, Windows, and Linux
Hello, World!
Glimmer code (from samples/hello/hello_world.rb):
require 'glimmer-dsl-tk'
include Glimmer
root {
title 'Hello, World!'
label {
text 'Hello, World!'
}
}.open
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_world'"
Glimmer app:

NOTE: Glimmer DSL for Tk is currently in early alpha mode (incomplete). Please help make better by contributing, adopting for small or low risk projects, and providing feedback. It is still an early alpha, so the more feedback and issues you report the better.
Other Glimmer DSL gems:
- glimmer-dsl-swt: Glimmer DSL for SWT (JRuby Desktop Development GUI Framework)
- glimmer-dsl-libui: Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development GUI Library)
- glimmer-dsl-opal: Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps)
- glimmer-dsl-xml: Glimmer DSL for XML (& HTML)
- glimmer-dsl-css: Glimmer DSL for CSS (Cascading Style Sheets)
Table of Contents
- Glimmer DSL for Tk
- MRI Ruby Desktop Development GUI Library
- Pre-requisites
- Setup
- Option 1: Install
- Option 2: Bundler
- Girb (Glimmer IRB)
- Tk Concepts
- Glimmer GUI DSL Concepts
- Supported Widgets
- Smart Defaults and Conventions
- The Grid Geometry Manager
- Data-Binding
- Label Data-Binding
- Combobox Data-Binding
- List Single Selection Data-Binding
- List Multi Selection Data-Binding
- Entry Data-Binding
- Spinbox Data-Binding
- Checkbutton Data-Binding
- Radiobutton Data-Binding
- Command Callback
- Gotchas
- Samples
- Hello, World!
- Hello, Button!
- Hello, Checkbutton!
- Hello, Radiobutton!
- Hello, Frame!
- Hello, Root!
- Hello, Notebook!
- Hello, Label!
- Hello, Message Box!
- Hello, Combobox!
- Hello, List Single Selection!
- Hello, List Multi Selection!
- Hello, Entry!
- Hello, Text!
- Hello, Spinbox!
- Hello, Computed!
- Help
- Issues
- Chat
- Process
- Planned Features and Feature Suggestions
- Change Log
- Contributing
- Contributors
- License
Pre-requisites
- Tcl/Tk: Follow the install instructions
- Ruby: On Windows, obtain from the Ruby download page. On the Mac and Linux, it is more convenient to just use RVM and follow the RVM Tk instructions.
For example, on the Mac, you can:
- Install the ActiveTcl Mac package from ActiveState.com
- Install RVM by running
\curl -sSL https://get.rvm.io | bash -s stable(and runcurl -sSL https://rvm.io/pkuczynski.asc | gpg --import -if needed for mentioned security reasons) - Run:
rvm install 3.0.2 --enable-shared --enable-pthread --with-tk --with-tcl - Run:
gem install tk -v0.4.0
Afterwards, if you open irb, you should be able to run require 'tk' successfully.
Setup
Option 1: Install
Run this command to install directly:
gem install glimmer-dsl-tk
Option 2: Bundler
Add the following to Gemfile:
gem 'glimmer-dsl-tk', '~> 0.0.26'
And, then run:
bundle
Girb (Glimmer IRB)
You can run the girb command (bin/girb if you cloned the project locally):
girb
This gives you irb with the glimmer-dsl-tk gem loaded and the Glimmer module mixed into the main object for easy experimentation with GUI.
Tk Concepts
Here is a summary taken from the official Tk Concepts Tutorial
Tk Concepts consist of:
- Widgets: Widgets are all the things that you see onscreen. In our example, we had a button, an entry, a few labels, and a frame. Others are things like checkboxes, tree views, scrollbars, text areas, and so on. Widgets are what are often referred to as "controls"; you'll also often see them referred to as "windows," particularly in Tk's documentation, a holdover from its X11 roots (so under that terminology, both a toplevel window and things like a button would be called windows).
- Geometry Management: If you've been playing around creating widgets, you've probably noticed that just by creating them, they didn't end up showing up onscreen. Having things actually put in the onscreen window, and precisely where in the window they show up is a separate step called geometry management.
- Event Handling: In Tk, as in most other user interface toolkits, there is an event loop which receives events from the operating system. These are things like button presses, keystrokes, mouse movement, window resizing, and so on.
Learn more at the official Tk Concepts Tutorial
Glimmer GUI DSL Concepts
The Glimmer GUI DSL provides a declarative syntax for Tk that:
- Supports smart defaults (e.g. grid layout on most widgets)
- Automates wiring of widgets (e.g. nesting a label under a toplevel root or adding a frame to a notebook)
- Hides lower-level details (e.g. main loop is started automatically when opening a window)
- Nests widgets according to their visual hierarchy
- Requires the minimum amount of syntax needed to describe an app's GUI
The Glimmer GUI DSL follows these simple concepts in mapping from Tk syntax:
- Widget Keyword: Any Tk widget (e.g.
Tk::Tile::Label) or toplevel window (e.g.TkRoot) may be declared by its lower-case underscored name without the namespace (e.g.labelorroot). This is called a keyword and is represented in the Glimmer GUI DSL by a Ruby method behind the scenes. - Args: Any keyword method may optionally take arguments surrounded by parentheses (e.g. a
framenested under anotebookmay receive tab options likeframe(text: 'Users'), which gets used behind the scenes by Tk code such asnotebook.add tab, text: 'Users') - Content/Options Block: Any keyword may optionally be followed by a Ruby curly-brace block containing nested widgets (content) and attributes (options). Attributes are simply Tk option keywords followed by arguments and no block (e.g.
title 'Hello, World!'under aroot) - Event Binding Block:
on(event) {}keyword receiving a Tk binding event name (e.g.KeyPressorComboboxSelected). No need to surround event by<>as Glimmer DSL for Tk takes care of that automatically.
Example of an app written in Tk imperative syntax:
root = TkRoot.new
root.title = 'Hello, Notebook!'
notebook = ::Tk::Tile::Notebook.new(root).grid
tab1 = ::Tk::Tile::Frame.new(notebook).grid
notebook.add tab1, text: 'English'
label1 = ::Tk::Tile::Label.new(tab1).grid
label1.text = 'Hello, World!'
tab2 = ::Tk::Tile::Frame.new(notebook).grid
notebook.add tab2, text: 'French'
label2 = ::Tk::Tile::Label.new(tab2).grid
label2.text = 'Bonjour, Univers!'
root.bind('KeyPress') do |event|
case event.keysym
when 'e', 'E'
notebook.select(0)
when 'f', 'F'
notebook.select(1)
end
end
root.mainloop
Example of the same app written in Glimmer declarative syntax:
root {
title 'Hello, Notebook!'
@notebook = notebook {
frame(text: 'English') {
label {
text 'Hello, World!'
}
}
frame(text: 'French') {
label {
text 'Bonjour, Univers!'
}
}
}
on('KeyPress') do |event|
case event.keysym
when 'e', 'E'
@notebook.select(0)
when 'f', 'F'
@notebook.select(1)
end
end
}.open
Supported Widgets
| keyword(args) | attributes | event bindings & callbacks |
|---|---|---|
button |
text, image (optional keyword args: subsample, zoom, from, to, shrink, compositingrule), compound ('center', 'top', 'bottom', 'left', 'right'), default ('active', 'normal') |
command {} |
checkbutton |
text, variable (Boolean), image (optional keyword args: subsample, zoom, from, to, shrink, compositingrule), compound ('center', 'top', 'bottom', 'left', 'right'), onvalue (default: 1), offvalue (default: 0) |
command {} |
combobox |
state, text |
'ComboboxSelected' |
entry |
width, text, validate, show ('none', 'focus', 'focusin', 'focusout', 'key', or 'all') |
'validate', 'invalid', 'change', validatecommand {}, invalidcommand {} |
spinbox |
text, from, to, increment, format, more attributes |
command {}, 'increment', 'decrement' |
frame(text: nil) |
width, height, borderwidth, relief ('flat' (default), 'raised', 'sunken', 'solid', 'ridge', 'groove') |
None |
label |
text, image (optional keyword args: subsample, zoom, from, to, shrink, compositingrule), compound ('center', 'top', 'bottom', 'left', 'right'), font ('default', 'text', 'fixed', 'menu', 'heading', 'caption', 'small_caption', 'icon', 'tooltip'), relief ('flat' (default), 'raised', 'sunken', 'solid', 'ridge', 'groove'), justify ('left', 'center', 'right'), foreground, background |
None |
list |
selectmode, selection |
None |
message_box(type: , message: , detail: , title: , icon: , default: , parent: ) |
None | None |
notebook |
None | None |
radiobutton |
text, variable (Boolean), image (optional keyword args: subsample, zoom, from, to, shrink, compositingrule), compound ('center', 'top', 'bottom', 'left', 'right'), value (default: text) |
command {} |
root |
title, iconphoto, background, alpha, fullscreen?, topmost?, transparent?, stackorder, winfo_screendepth, winfo_screenvisual, winfo_screenwidth, winfo_screenheight, winfo_pixels('li'), winfo_screen, wm_maxsize, state ('normal', 'iconic', 'withdrawn', 'icon', 'zoomed') |
'DELETE_WINDOW', 'OPEN_WINDOW' |
text |
text, many more attributes |
'modified', 'selection' |
Common Attributes
grid:Hashof:row,:column,:padx,:pady,:sticky('e', 'w', 'n', 's'or any combination of direction letters)
Common Event Bindings
ActivateDeactivateMouseWheelKeyPressKeyReleaseButtonPressButtonReleaseMotionConfigureMapUnmapVisibilityExposeDestroyFocusInFocusOutEnterLeavePropertyColormapMapRequestCirculateRequestResizeRequestConfigureRequestCreateGravityReparentCirculate
Common Themed Widget States
active?disabled?focus?pressed?selected?background?readonly?alternate?invalid?hover?
Smart Defaults and Conventions
Event Bindings
Any events that normally can be accepted by the Tk bind or protocol methods can be accepted by the on(event) {} listener syntax. For actual (non-virtual) events, there is no need to surround event name by <> as Glimmer DSL for Tk automatically takes care of that when needed and leaves out when not needed.
Grid Layout
grid layout with sticky: 'nsew' is the default on all widgets except toplevel widgets.
Also, any widget that is the first in a series of siblings has column_weight as 1 to automatically resize with window resizing by default.
To override that behavior, you may set alternative grid keyword args if needed (e.g. grid sticky: '', column_weight: 0 disables the smart defaults).
Label/Button Image
Label and Button image attribute can accept image path directly as an alternative to TkPhotoImage object in addition to key values for automatic processing of image (subsample, zoom, from, to, shrink, compositingrule)
Notebook Frame
When nesting frame under notebook, you can pass a :text keyword argument to indicate the tab title.
Icon Photo
The iconphoto attribute on root is set to the Glimmer icon by default if no icon photo is supplied.
Otherwise, Glimmer DSL for Tk is smart enough to accept an image path directly (no need to wrap with TkPhotoImage)
Example with direct image path (you may copy/paste in girb):
root {
title 'Title'
iconphoto 'icons/glimmer.png'
}.open
Example with TkPhotoImage object (you may copy/paste in girb):
root {
title 'Title'
iconphoto TkPhotoImage.new(file: 'icons/glimmer.png')
}.open
Root Background
root background color attribute is automatically set to '#ececec' on the Mac to avoid having a non-native-looking light-colored background.
The Grid Geometry Manager
The Grid Geometry Manager is supported via the grid keyword just as per the Tk documentation, except by nesting under the widget it concerns.
Example:
label {
grid column: 0, row: 2, sticky: 'w'
text 'Year of Birth: '
}
entry {
grid column: 1, row: 2
width 15
text <=> [@contact, :year_of_birth]
}
Extra convenience options may be passed to grid when using Glimmer DSL for Tk:
min_width: alias forcolumnminsizebeing called onTkGrid.columnconfiguremin_height: alias forrowminsizebeing called onTkGrid.rowconfigurecolumn_weight: alias forcolumnweightbeing called onTkGrid.columnconfigurerow_weight: alias forrowweightbeing called onTkGrid.rowconfigure
Note also the Grid Layout conventions (e.g. column_weight is automatically set to 1 for the first widget in a series of siblings to automatically have all resize when window resize)
More details can be found in the Hello, Computed! sample below.
Data-Binding
Glimmer supports Shine syntax bidirectional data-binding via the <=> operator (read-write) and unidirectional data-binding via the <= operator (read-only), which takes a model and an attribute (the bind keyword may also be used as the old-style of data-binding).
Label Data-Binding
Example:
This assumes a Person model with a country attribute.
label {
text <= [person, :country]
}
That code binds the textvariable value of the label unidirectionally (read-only) to the country property on the person model.
It automatically handles all the Tk plumbing behind the scenes.
More details can be found in the Hello, Computed! sample below.
Combobox Data-Binding
Example:
This assumes a Person model with a country attribute representing their current country and a country_options attribute representing available options for the country attribute.
combobox {
readonly true # this applies to text editing only (item selection still triggers a write to model)
text <=> [person, :country]
}
That code sets the values of the combobox to the country_options property on the person model (data-binding attribute + "_options" by convention).
It also binds the text selection of the combobox to the country property on the person model.
It automatically handles all the Tk plumbing behind the scenes, such as using TkVariable and setting combobox values from person.country_options by convention (attribute_name + "_options").
More details can be found in the Hello, Combobox! sample below.
List Single Selection Data-Binding
Tk does not support a native themed listbox, so Glimmer implements its own list widget on top of Tk::Tile::Treeview. It is set to single selection via selectmode 'browse'.
Example:
This assumes a Person model with a country attribute representing their current country and a country_options attribute representing available options for the country attribute.
list {
selectmode 'browse'
text <=> [person, :country]
}
That code binds the items text of the list to the country_options attribute on the person model (data-binding attribute + "_options" by convention).
It also binds the selection text of the list to the country attribute on the person model.
It automatically handles all the Tk plumbing behind the scenes.
More details can be found in the Hello, List Single Selection! sample below.
List Multi Selection Data-Binding
Tk does not support a native themed listbox, so Glimmer implements its own list widget on top of Tk::Tile::Treeview. It is set to multi selection by default.
Example:
This assumes a Person model with a provinces attribute representing their current country and a provinces_options attribute representing available options for the provinces attribute.
list {
text <=> [person, :provinces]
}
That code binds the items text of the list to the provinces_options attribute on the person model (data-binding attribute + "_options" by convention).
It also binds the selection text of the list to the provinces attribute on the person model.
It automatically handles all the Tk plumbing behind the scenes.
More details can be found in the Hello, List Multi Selection! sample below.
Entry Data-Binding
Example:
This assumes a Person model with a country attribute.
entry {
text <=> [person, :country]
}
That code binds the textvariable value of the entry to the country attribute on the person model.
It automatically handles all the Tk plumbing behind the scenes.
More details can be found in Hello, Entry! and Hello, Computed! samples below.
Spinbox Data-Binding
Example:
This assumes a Person model with a donation attribute.
spinbox {
from 1.0 # minimum value
to 150.0 # maximum value
increment 5.0 # increment on up and down
format '%0.2f'
text <=> [person, :country]
}
That code binds the textvariable value of the spinbox to the donation attribute on the person model.
It automatically handles all the Tk plumbing behind the scenes.
More details can be found in Hello, Spinbox! sample below.
Text Data-Binding
Example:
This assumes a Person model with a address attribute.
text {
text <=> [person, :address]
}
That code binds the text content of text to the address attribute on the person model.
It automatically handles all the Tk plumbing behind the scenes (including fine-grained inserts and deletes, abstracting them all away).
More details can be found in Glimmer Meta-Sample below.
Checkbutton Data-Binding
Example:
This assumes a Person model with a boolean adult attribute.
{
variable <=> [person, :adult]
}
That code binds the variable value of the checkbutton to the boolean adult attribute on the person model.
It automatically handles all the Tk plumbing behind the scenes.
If you need to display a half-checked checkbutton, bind to alternate attribute.
More details can be found in the Hello, Checkbutton! sample below.
Radiobutton Data-Binding
Example:
This assumes a Person model with boolean male and female attributes.
{
text 'Male'
variable <=> [@person, :male]
}
{
text 'Female'
variable <=> [@person, :female]
}
That code binds the variable value of the radiobutton to the boolean male and female attributes on the person model.
It automatically handles all the Tk plumbing behind the scenes, including setting the radiobutton value (uses text attribute as value), enabling an API that works with simple booleans for each radiobutton.
For very rare cases, if you need to display a half-selected radiobutton, set alternate attribute as true when the variable value is false.
More details can be found in the Hello, Radiobutton! sample below.
Command Callback
button, spinbox, radiobutton and checkbutton can set a command block to trigger when the user clicks the button/checkbutton. This may be done with the command keyword, passing in a block directly.
Example:
{
text "Reset Selection"
command {
person.reset_country
}
}
Alternatively, it can be treated as simply an event for consistency with other event bindings:
{
text "Reset Selection"
on('command') do
person.reset_country
end
}
More details can be found in the Hello, Button! sample below.
Gotchas
- Setting
backgroundattribute onframeorlabeldoes not work in'aqua'theme on the Mac (only in'classic'theme)
Samples
The easiest way to run samples is by launching the Glimmer Meta-Sample (the Sample of Samples).
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/elaborate/meta_sample'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/elaborate/.rb

Hello, World!
Glimmer code (from samples/hello/hello_world.rb):
require 'glimmer-dsl-tk'
include Glimmer
root {
title 'Hello, World!'
label {
text 'Hello, World!'
}
}.open
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_world'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/hello/hello_world.rb
Glimmer app:

Hello, Button!
Glimmer code (from samples/hello/hello_button.rb):
require 'glimmer-dsl-tk'
class HelloButton
include Glimmer
attr_accessor :count
def initialize
@count = 0
end
def launch
root {
title 'Hello, Button!'
frame {
grid row: 0, column: 0
label {
grid pady: 15
text 'Text Button'
}
{
text <= [self, :count, on_read: ->(value) { "Click To Increment: #{value} " }]
default 'active'
focus true
command {
self.count += 1
}
}
}
frame {
grid row: 0, column: 1
label {
grid pady: 15
text 'Image Button'
}
{
image File.('../../icons/glimmer.png', __dir__), subsample: 5
command {
(title: 'Image Button', message: 'Image Button Clicked!')
}
}
}
frame {
grid row: 0, column: 2
label {
grid pady: 15
text 'Text Image Buttons'
}
['center', 'top', 'bottom', 'left', 'right'].each do |compound_option|
{
image File.('../../icons/glimmer.png', __dir__), subsample: 5
text 'Text Image Button'
compound compound_option
command {
(title: 'Text Image Button', message: 'Text Image Button Clicked!', detail: "(#{compound_option})")
}
}
end
}
}.open
end
end
HelloButton.new.launch
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_button'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/hello/.rb
Glimmer app:

Hello, Checkbutton!
Glimmer code (from samples/hello/hello_checkbutton.rb):
require 'glimmer-dsl-tk'
class
class Person
attr_accessor :skiing, :snowboarding, :snowmobiling, :snowshoeing, :snow_activities, :snow_activities_alternate
def initialize
reset_activities!
individual_observer = Glimmer::DataBinding::Observer.proc do
unless @updating_group
@updating_individual = true
if skiing && snowboarding && snowmobiling && snowshoeing
self.snow_activities = true
self.snow_activities_alternate = false
elsif skiing || snowboarding || snowmobiling || snowshoeing
self.snow_activities = true
self.snow_activities_alternate = true
else
self.snow_activities = false
self.snow_activities_alternate = false
end
@updating_individual = false
end
end
individual_observer.observe(self, :skiing)
individual_observer.observe(self, :snowboarding)
individual_observer.observe(self, :snowmobiling)
individual_observer.observe(self, :snowshoeing)
group_observer = Glimmer::DataBinding::Observer.proc do
unless @updating_individual
@updating_group = true
self.skiing = self.snow_activities
self.snowboarding = self.snow_activities
self.snowmobiling = self.snow_activities
self.snowshoeing = self.snow_activities
@updating_group = false
end
end
group_observer.observe(self, :snow_activities)
end
def reset_activities!
self.snow_activities = true
self.snow_activities_alternate = true
self.skiing = false
self.snowboarding = true
self.snowmobiling = false
self.snowshoeing = false
end
end
include Glimmer
def initialize
@person = Person.new
end
def launch
root {
title 'Hello, Checkbutton!'
background '#ececec' if OS.mac?
label {
text 'Check all snow activities you are interested in:'
font 'caption'
}
frame {
{
text 'Snow Activities'
variable <=> [@person, :snow_activities]
alternate <=> [@person, :snow_activities_alternate] # binds half-checked state
}
frame {
{
text 'Skiing'
variable <=> [@person, :skiing]
}
{
text 'Snowboarding'
variable <=> [@person, :snowboarding]
}
{
text 'Snowmobiling'
variable <=> [@person, :snowmobiling]
}
{
text 'Snowshoeing'
variable <=> [@person, :snowshoeing]
}
}
}
{
text 'Reset Activities'
command do
@person.reset_activities!
end
}
}.open
end
end
.new.launch
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_checkbutton'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/hello/.rb
Glimmer app:



Hello, Radiobutton!
Glimmer code (from samples/hello/hello_radiobutton.rb):
require 'glimmer-dsl-tk'
class
class Person
attr_accessor :male, :female, :child, :teen, :adult, :senior
def initialize
reset!
end
def reset!
self.male = true
self.female = nil
self.child = nil
self.teen = nil
self.adult = true
self.senior = nil
end
end
include Glimmer
def initialize
@person = Person.new
end
def launch
root {
title 'Hello, Radio!'
background '#ececec' if OS.mac?
label {
text 'Gender:'
font 'caption'
}
frame {
{
text 'Male'
variable <=> [@person, :male]
}
{
text 'Female'
variable <=> [@person, :female]
}
}
label {
text 'Age Group:'
font 'caption'
}
frame {
{
text 'Child'
variable <=> [@person, :child]
}
{
text 'Teen'
variable <=> [@person, :teen]
}
{
text 'Adult'
variable <=> [@person, :adult]
}
{
text 'Senior'
variable <=> [@person, :senior]
}
}
{
text 'Reset'
command do
@person.reset!
end
}
}.open
end
end
.new.launch
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_radiobutton'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/hello/.rb
Glimmer app:

Hello, Frame!
Glimmer code (from samples/hello/hello_frame.rb):
require 'glimmer-dsl-tk'
class HelloFrame
include Glimmer
def launch
root {
title 'Hello, Frame!'
frame { # frame simply contains widgets for visual organization via a layout
relief 'sunken'
label {
grid row: 0, column: 0, columnspan: 4, padx: 5, pady: 5
text 'Enter Your Full Name'
font 'caption'
anchor 'center'
}
label {
grid row: 1, column: 0, padx: 5
text "First Name:"
}
entry {
grid row: 1, column: 1, padx: 5
}
label {
grid row: 2, column: 0, padx: 5
text "Last Name:"
}
entry {
grid row: 2, column: 1, padx: 5
}
}
frame { # an empty frame can specify width and height
grid padx: 7, pady: 7
width 200
height 100
borderwidth 3
relief 'sunken'
}
}.open
end
end
HelloFrame.new.launch
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_frame'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/hello/hello_frame.rb
Glimmer app:

Hello, Root!
Glimmer code (from samples/hello/hello_frame.rb):
require 'glimmer-dsl-tk'
include Glimmer
root { |r|
title 'Hello, Root!'
iconphoto File.('../../icons/glimmer.png', __dir__)
width 400
height 200
x -150
y 300
resizable true # same as `resizable true, true`, meaning cannot resize horizontally and vertically
minsize 200, 100
maxsize 600, 400
background 'lightgrey'
alpha 0.85 # on the mac, you can set `transparent true` as well
topmost true
on('OPEN_WINDOW') do # custom event that runs right after Tk.mainloop
(parent: r, title: 'Hi', message: 'Hi')
end
on('DELETE_WINDOW') do |event| # alias for WM_DELETE_WINDOW protocol event
(parent: r, title: 'Bye', message: 'Bye')
exit(0)
end
}.open
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_root'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/hello/hello_root.rb
Glimmer app:



Hello, Notebook!
Glimmer code (from samples/hello/hello_notebook.rb):
require 'glimmer-dsl-tk'
class HelloNotebook
include Glimmer
def launch
root {
title 'Hello, Notebook!'
notebook {
frame(text: 'English') {
label {
text 'Hello, World!'
}
}
frame(text: 'French') {
label {
text 'Bonjour, Univers!'
}
}
}
}.open
end
end
HelloNotebook.new.launch
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_notebook'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/hello/hello_notebook.rb
Glimmer app:

Hello, Label!
Glimmer code (from samples/hello/hello_label.rb):
require 'glimmer-dsl-tk'
class HelloLabel
include Glimmer
LABEL_FONTS = ['default', 'text', 'fixed', 'menu', 'heading', 'caption', 'small_caption', 'icon', 'tooltip']
LABEL_RELIEFS = ['flat', 'raised', 'sunken', 'solid', 'ridge', 'groove']
def launch
root {
title 'Hello, Label!'
notebook {
frame(text: 'left-aligned') {
3.times do |n|
label {
text "Field #{n + 1} / default font / flat relief"
width 60
anchor 'w'
}
end
}
frame(text: 'centered') {
9.times do |n|
label {
text "Field #{n + 1} / fixed font / raised relief"
width 80
anchor 'center'
font 'fixed'
relief 'raised'
}
end
}
frame(text: 'right-aligned') {
9.times do |n|
label {
text "Field #{n + 1} / small_caption font / ridge relief"
width 80
anchor 'e'
font 'small_caption'
relief 'ridge'
}
end
}
frame(text: 'justified') {
label {
text " This is a very long paragraph that spans multiple lines. It looks better and\n is easier to read when justified. In fact, this is how to make it look like a\n new paper column. That results in professional looking text. Magazines use the\n same sort of justified text. Word processors also support justified text. In\n fact, this is how text is made for news papers and magainzes, and then published\n in paper form and digitally.\n MULTI_LINE_STRING\n width 60\n anchor 'center'\n justify 'center' # other options are: 'left' and 'right'\n font 'caption' # other options are: 'default', 'text', 'fixed', 'menu', 'heading', 'small_caption', 'icon', 'tooltip'\n foreground 'blue'\n relief 'sunken' # other options are: 'flat' (default), 'raised', 'solid', 'ridge', 'groove'\n }\n }\n\n frame(text: 'images') {\n ['denmark', 'finland', 'norway'].each do |image_name|\n label {\n image File.expand_path(\"images/\#{image_name}.png\", __dir__)\n }\n end\n }\n\n frame(text: 'background images') {\n ['italy', 'france', 'mexico'].each do |image_name|\n label {\n text image_name.capitalize\n image File.expand_path(\"images/\#{image_name}.png\", __dir__)\n compound 'center'\n }\n end\n }\n }\n }.open\n end\nend\n\nHelloLabel.new.launch\n"
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_label'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/hello/hello_label.rb
Glimmer app:






Hello, Message Box!
Glimmer code (from samples/hello/hello_message_box.rb):
root { |r|
title 'Hello, Message Box!'
frame {
grid sticky: 'nsew', padx: 15, pady: 15
{
text 'Please Click To Win a Surprise'
command {
# specifying parent ensures dialog shows up centered on top of window (instead of centered in display monitor)
@result_label.text = (parent: r, title: 'Surprise', message: "Congratulations!\n\nYou won $1,000,000!") # type: 'ok' by default
}
}
{
text 'Download Software Update'
command {
@result_label.text = (type: 'okcancel', title: 'Software Update', message: "We will begin to download software update.")
}
}
{
text 'Format Hard Drive'
command {
@result_label.text = (type: 'yesno', icon: 'question', title: 'Format', message: "Would you like to format your hard drive?")
}
}
{
text 'Submit Application'
command {
@result_label.text = (type: 'yesnocancel', icon: 'question', title: 'Application', message: "Would you like to review application before submitting?")
}
}
{
text 'Play Video'
command {
@result_label.text = (type: 'retrycancel', icon: 'error', title: 'Video Replay', message: "Video has failed to play. Would you like to retry?")
}
}
{
text 'Installation Completed'
command {
@result_label.text = (type: 'abortretryignore', icon: 'warning', default: 'ignore', title: 'Failed To Install Extra Utilities', message: "Installation is complete, but extra utilities have failed to install. Would you like to retry installing extra utilities?", detail: 'Encountered network error in downloading extra utilities, resulting in failure to install them')
}
}
}
frame {
grid sticky: 'nsew', padx: 15, pady: 15
label {
grid row: 0, column: 0
text 'Result:'
}
@result_label = label {
grid row: 0, column: 1
}
}
}.open
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_message_box'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/hello/.rb
Glimmer app:

Hello, Combobox!
Glimmer code (from samples/hello/hello_combobox.rb):
# ... more code precedes
root {
title 'Hello, Combobox!'
combobox {
readonly true # this applies to text editing only (item selection still triggers a write to model)
text <=> [person, :country]
}
{
text "Reset Selection"
command {
person.reset_country
}
}
}.open
# ... more code follows
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_combobox'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/hello/hello_combobox.rb
Glimmer app:

Hello, List Single Selection!
Glimmer code (from samples/hello/hello_list_single_selection.rb):
# ... more code precedes
root {
title 'Hello, List Single Selection!'
list {
selectmode 'browse'
selection <=> [person, :country]
}
{
text "Reset Selection To Default Value"
command { person.reset_country }
}
}.open
# ... more code follows
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_list_single_selection'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/hello/hello_list_single_selection.rb
Glimmer app:

Hello, List Multi Selection!
Glimmer code (from samples/hello/hello_list_multi_selection.rb):
# ... more code precedes
root {
title 'Hello, List Multi Selection!'
list {
selection <=> [person, :provinces]
}
{
text "Reset Selection To Defaults"
command { person.reset_provinces }
}
}.open
# ... more code follows
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_list_multi_selection'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/hello/hello_list_multi_selection.rb
Glimmer app:

Hello, Entry!
Glimmer code (from samples/hello/hello_entry.rb):
require 'glimmer-dsl-tk'
class HelloEntry
include Glimmer
attr_accessor :default, :password, :telephone, :read_only
def initialize
self.default = 'default'
self.password = 'password'
self.telephone = '555-555-5555'
self.read_only = 'Telephone area code is 555'
end
def launch
root {
title 'Hello, Entry!'
label {
grid sticky: 'ew'
text 'default entry'
}
entry {
grid sticky: 'ew'
text <=> [self, :default]
}
label {
grid sticky: 'ew'
text 'password entry'
}
entry {
grid sticky: 'ew'
show '*'
text <=> [self, :password]
}
@validated_entry_label = label {
grid sticky: 'ew'
text 'entry with event handlers'
}
entry {
grid sticky: 'ew'
text <=> [self, :telephone]
validate 'key'
## this event kicks in just after the user typed and before modifying the text variable
on('validate') do |new_text_variable|
telephone?(new_text_variable.value)
end
## this event kicks in just after the text variable is validated and before it is modified
on('invalid') do |validate_args|
@validated_entry_label.text = "#{validate_args.string} is not valid!"
@validated_entry_label.foreground = 'red'
end
## this event kicks in just after the text variable is validated and modified
on('change') do |new_text_variable|
self.read_only = "Telephone area code is #{new_text_variable.value.gsub(/[^0-9]/, '')[0...3]}"
@validated_entry_label.text = 'entry with event handlers'
@validated_entry_label.foreground = nil
end
}
label {
grid sticky: 'ew'
text 'read-only entry'
}
entry {
grid sticky: 'ew'
text <=> [self, :read_only]
readonly true
}
}.open
end
def telephone?(text)
!!text.match(/^\d{0,3}[-.\/]?\d{0,3}[-.\/]?\d{0,4}$/)
end
end
HelloEntry.new.launch
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_entry'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/hello/hello_entry.rb
Glimmer app:

Hello, Text!
Glimmer DSL for Tk automatically provides a text attribute for the text widget that enables updating its content simply without worrying about whether to manually insert by index, delete, or append.
Glimmer code (from samples/hello/hello_text.rb):
require 'glimmer-dsl-tk'
class HelloText
include Glimmer
COLOR_OPTIONS = %w[black purple blue green orange yellow red white].map(&:capitalize)
FOREGROUND_PROMPT = '<select foreground>'
BACKGROUND_PROMPT = '<select background>'
def initialize
@foreground = FOREGROUND_PROMPT
@background = BACKGROUND_PROMPT
end
attr_accessor :foreground
def
[FOREGROUND_PROMPT] + COLOR_OPTIONS
end
attr_accessor :background
def
[BACKGROUND_PROMPT] + COLOR_OPTIONS
end
def launch
root {
title 'Hello, Text!'
frame {
grid row: 0, column: 0
combobox {
grid row: 0, column: 0, column_weight: 1
readonly true
text <=> [self, :foreground, after_write: ->(value) { @text.add_selection_format('foreground', value == FOREGROUND_PROMPT ? 'black' : value) }]
}
combobox {
grid row: 0, column: 1, column_weight: 1
readonly true
text <=> [self, :background, after_write: ->(value) { @text.add_selection_format('background', value == BACKGROUND_PROMPT ? 'black' : value) }]
}
}
@text = text {
grid row: 1, column: 0, row_weight: 1
text " According to the National Post, a heavy metal-loving high school principal in Canada will be allowed to keep her job, days after a public campaign to oust her made headlines around the globe.\n\n Parents at Eden High School in St. Catharines, Ontario launched a petition to remove principal Sharon Burns after discovering she's an IRON MAIDEN fan.\n\n The petition, titled \"Eden High School Principal, Sharon Burns, Needs to Be Transferred Immediately!\" read, \"We are deeply disturbed that the principal assigned to the school blatantly showed Satanic symbols and her allegiance to Satanic practices on her public social media platforms where all the students can see them under @edenprincipal (not her personal account).\"\n\n More than 500 people signed the petition to transfer Burns after she posted a picture of herself flashing the \"devil horns\" hand sign with a doll of the MAIDEN zombie mascot Eddie behind her as well as a personalized license plate reading \"IRNMADEN\" and a handwritten note that reads \"Eddie 666\" on a car's dashboard.\n\n\n The number 666 is used to represent the devil, and is featured prominently in MAIDEN's artwork by the band, whose classic third album is titled \"The Number Of The Beast\".\n\n The petition was later updated to state that the demand for her transfer is not because of her love for metal, but for \"openly displaying her own handmade sign with the 666 clearly displayed on it\".\n\n The creator of the original petition wrote: \"Sharon knows full well what she did was simply inappropriate, unnecessary and not professional but has yet to publicly admit so and is willing to allow people to believe a completely different story, making very real concerns seem petty.\"\n\n Meanwhile, a counter-petition supporting Burns garnered over 20,000 signatures.\n\n \"It is ridiculous that a couple of parents judge her role as a principal only based on an Instagram post. (About liking the band IRON MAIDEN. That's it.) Eden High School is a public school. Not a Christian school,\" the petition titled \"We need Mrs Burns\" stated. \"She has made Eden a safe space for so many people. She spreads nothing but love and kindness.\"\n\n After the complaints were aired, the District School Board of Niagara spoke with Burns and the parents who launched the petition, and the issue is over as far as the board is concerned, Kim Sweeney, the board's chief communications officer, told the National Post. No disciplinary action or policy changes were needed.\n\n \"Our belief is that taste in music is subjective and we support that both students and staff enjoy a wide variety of genres,\" Sweeney said.\n\n The original petition has since been removed.\n MULTI_LINE_STRING\n }\n }.open\n end\nend\n\nHelloText.new.launch\n"
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_text'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/hello/hello_text.rb
Glimmer app:

Hello, Spinbox!
Glimmer code (from samples/hello/hello_spinbox.rb):
require 'glimmer-dsl-tk'
class HelloSpinbox
class Person
attr_accessor :donation
end
include Glimmer
def initialize
@person = Person.new
@person.donation = 5.0 # in dollars
end
def launch
root {
title 'Hello, Spinbox!'
label {
text 'Please select the amount you would like to donate to the poor:'
}
frame {
label {
grid row: 0, column: 0
text 'Amount:'
font 'caption'
}
label {
grid row: 0, column: 1
text '$'
}
spinbox { |sb|
grid row: 0, column: 2
from 1.0 # minimum value
to 150.0 # maximum value
increment 5.0 # increment on up and down
format '%0.2f'
text <=> [@person, :donation]
}
label {
grid row: 1, column: 0, columnspan: 3
text <=> [@person, :donation, on_read: ->(value) { "Thank you for your donation of $#{"%.2f" % value.to_f}"}]
}
}
}.open
end
end
HelloSpinbox.new.launch
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_spinbox'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/hello/hello_spinbox.rb
Glimmer app:

Hello, Computed!
Glimmer code (from samples/hello/hello_computed.rb):
# ... more code precedes
root {
title 'Hello, Computed!'
frame {
grid column: 0, row: 0, padx: 5, pady: 5
label {
grid column: 0, row: 0, sticky: 'w'
text 'First Name: '
}
entry {
grid column: 1, row: 0
width 15
text <=> [@contact, :first_name]
}
label {
grid column: 0, row: 1, sticky: 'w'
text 'Last Name: '
}
entry {
grid column: 1, row: 1
width 15
text <=> [@contact, :last_name]
}
label {
grid column: 0, row: 2, sticky: 'w'
text 'Year of Birth: '
}
entry {
grid column: 1, row: 2
width 15
text <=> [@contact, :year_of_birth]
}
label {
grid column: 0, row: 3, sticky: 'w'
text 'Name: '
}
label {
grid column: 1, row: 3, sticky: 'w'
text <=> [@contact, :name, computed_by: [:first_name, :last_name]]
}
label {
grid column: 0, row: 4, sticky: 'w'
text 'Age: '
}
label {
grid column: 1, row: 4, sticky: 'w'
text <=> [@contact, :age, on_write: :to_i, computed_by: [:year_of_birth]]
}
}
}.open
# ... more code follows
Run with glimmer-dsl-tk gem installed:
ruby -r glimmer-dsl-tk -e "require 'samples/hello/hello_computed'"
Alternatively, run from cloned project without glimmer-dsl-tk gem installed:
ruby -r ./lib/glimmer-dsl-tk.rb samples/hello/hello_computed.rb
Glimmer app:

Help
Issues
You may submit issues on GitHub.
Click here to submit an issue.
Chat
Process
Planned Features and Feature Suggestions
These features have been planned or suggested. You might see them in a future version of Glimmer DSL for Tk. You are welcome to contribute more feature suggestions.
Change Log
Contributing
Contributors
- Andy Maleh (Founder)
Click here to view contributor commits.
License
Copyright (c) 2020-2021 - Andy Maleh.
--
Built for Glimmer (DSL Framework).