Glimmer 1.0.6 - DSL Framework
Contributors Wanted! (Submit a Glimmer App Sample to Get Started)
(The Original Glimmer Library Since 2007. Beware of Imitators!)
Glimmer started out as GUI Library and grew into a full-fledged DSL Framework. Glimmer's namesake is referring to the Glimmer of Ruby in Graphical User Interfaces (contrary to popular myth perpetrated by Charles Nutter, Glimmer has nothing to do with the ill-fated Whitney Houston movie, which does not in fact share the same name)
Glimmer is a DSL Framework that consists of two things:
- DSL Engine: enables building DSLs for desktop GUI, web GUI, XML/HTML documents, and CSS styling.
- Data-Binding Support: enables synchronizing GUI with Model Attributes bidirectionally.
Glimmer is the cream of the crop when it comes to building DSLs in Ruby:
- Supports building the tersest most concise domain specific language syntax in Ruby.
- No extra unnecessary block variables when not needed
- Maximum readability and maintainability
- DSL Blocks are true Ruby closures that can conveniently leverage variables from the outside and utilize standard Ruby code in and around. Just code in Ruby as usual and be happy! No surprising restrictions or strange uses of
instance_exec
/eval
. - DSL syntax is limited to classes that mixin the
Glimmer
module, so the rest of the code is fully safe from namespace pollution. - Multiple DSLs may be mixed together safely to achieve maximum expressability, composability, and productivity
- DSLs are fully configurable, so you may activate and deactivate DSLs as per your current needs only
Glimmer supports the following DSLs:
- glimmer-dsl-swt: Glimmer DSL for SWT (JRuby Desktop Development GUI Framework)
- glimmer-dsl-tk: Glimmer DSL for Tk (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
Glimmer and/or Glimmer DSLs receive two updates per month. You can trust Glimmer with your Ruby development needs.
Table of contents
- Glimmer 1.0.6
- Glimmer DSL for SWT (JRuby Desktop Development GUI Framework)
- Glimmer DSL for Tk (Ruby Desktop Development GUI Library)
- Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps)
- Glimmer DSL for XML (& HTML)
- Glimmer DSL for CSS
- DSL Engine
- Data-Binding Support
- Glimmer Supporting Libraries
- Glimmer Process
- Resources
- Help
- Issues
- Chat
- Feature Suggestions
- Change Log
- Contributing
- Contributors
- Hire Me
- License
Glimmer DSL for SWT (JRuby Desktop Development GUI Framework)
Glimmer DSL for SWT is a native-GUI cross-platform desktop development library written in JRuby, an OS-threaded faster version of Ruby. Glimmer's main innovation is a declarative Ruby DSL that enables productive and efficient authoring of desktop application user-interfaces while relying on the robust Eclipse SWT library. Glimmer DSL for SWT additionally innovates by having built-in data-binding support, which greatly facilitates synchronizing the GUI with domain models, thus achieving true decoupling of object oriented components and enabling developers to solve business problems (test-first) without worrying about GUI concerns. To get started quickly, Glimmer DSL for SWT offers scaffolding options for Apps, Gems, and Custom Widgets. Glimmer DSL for SWT also includes native-executable packaging support, sorely lacking in other libraries, thus enabling the delivery of desktop apps written in Ruby as truly native DMG/PKG/APP files on the Mac + App Store and MSI/EXE files on Windows.
To get started, visit the Glimmer DSL for SWT project page for instructions on installing the glimmer-dsl-swt gem.
Glimmer DSL for SWT Samples
Hello, World!
Glimmer GUI code (from samples/hello/hello_world.rb):
include Glimmer
shell {
text "Glimmer"
label {
text "Hello, World!"
}
}.open
Glimmer app:
Tic Tac Toe
Glimmer GUI code (from samples/elaborate/tic_tac_toe.rb):
# ...
@shell = 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)
font style: :bold, height: 20
{
@tic_tac_toe_board.mark(row, column)
}
}
}
}
}
}
# ...
Glimmer app:
Contact Manager
Glimmer GUI code (from samples/elaborate/contact_manager.rb):
# ...
shell {
text "Contact Manager"
composite {
group {
grid_layout(2, false) {
margin_width 0
margin_height 0
}
layout_data :fill, :center, true, false
text 'Lookup Contacts'
font height: 24
label {
layout_data :right, :center, false, false
text "First &Name: "
font height: 16
}
text {
layout_data :fill, :center, true, false
text bind(@contact_manager_presenter, :first_name)
on_key_pressed {|key_event|
@contact_manager_presenter.find if key_event.keyCode == swt(:cr)
}
}
label {
layout_data :right, :center, false, false
text "&Last Name: "
font height: 16
}
text {
layout_data :fill, :center, true, false
text bind(@contact_manager_presenter, :last_name)
on_key_pressed {|key_event|
@contact_manager_presenter.find if key_event.keyCode == swt(:cr)
}
}
label {
layout_data :right, :center, false, false
text "&Email: "
font height: 16
}
text {
layout_data :fill, :center, true, false
text bind(@contact_manager_presenter, :email)
on_key_pressed {|key_event|
@contact_manager_presenter.find if key_event.keyCode == swt(:cr)
}
}
composite {
row_layout {
margin_width 0
margin_height 0
}
layout_data(:right, :center, false, false) {
horizontal_span 2
}
{
text "&Find"
{ @contact_manager_presenter.find }
on_key_pressed {|key_event|
@contact_manager_presenter.find if key_event.keyCode == swt(:cr)
}
}
{
text "&List All"
{ @contact_manager_presenter.list }
on_key_pressed {|key_event|
@contact_manager_presenter.list if key_event.keyCode == swt(:cr)
}
}
}
}
table(:multi) { |table_proxy|
layout_data {
horizontal_alignment :fill
vertical_alignment :fill
grab_excess_horizontal_space true
grab_excess_vertical_space true
height_hint 200
}
table_column {
text "First Name"
width 80
}
table_column {
text "Last Name"
width 80
}
table_column {
text "Email"
width 200
}
items bind(@contact_manager_presenter, :results),
column_properties(:first_name, :last_name, :email)
on_mouse_up { |event|
table_proxy.edit_table_item(event.table_item, event.column_index)
}
}
}
}.open
# ...
Glimmer App:
Production Desktop Apps Built with Glimmer DSL for SWT
Are We There Yet? - Small Project Tracking App
Math Bowling - Elementary Level Math Game Featuring Bowling Rules
Glimmer DSL for Tk (Ruby Desktop Development GUI Library)
Tcl/Tk has evolved into a practical desktop GUI toolkit due to gaining truely native looking 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 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
To get started, visit the Glimmer DSL for Tk project page for instructions on installing the glimmer-dsl-tk gem.
Glimmer DSL for Tk Samples
Hello, World!
Glimmer code (from samples/hello/hello_world.rb):
include Glimmer
root {
label {
text 'Hello, World!'
}
}.open
Run (with the glimmer-dsl-tk gem installed):
ruby -r glimmer-dsl-tk -e "require '../samples/hello/hello_world.rb'"
Glimmer app:
Hello, Tab!
Glimmer code (from samples/hello/hello_tab.rb):
include Glimmer
root {
title 'Hello, Tab!'
notebook {
frame(text: 'English') {
label {
text 'Hello, World!'
}
}
frame(text: 'French') {
label {
text 'Bonjour, Univers!'
}
}
}
}.open
Run (with the glimmer-dsl-tk gem installed):
ruby -r glimmer-dsl-tk -e "require '../samples/hello/hello_tab.rb'"
Glimmer app:
Hello, Combo!
Glimmer code (from samples/hello/hello_combo.rb):
# ... more code precedes
root {
title 'Hello, Combo!'
combobox { |proxy|
state 'readonly'
text bind(person, :country)
}
{ |proxy|
text "Reset Selection"
command {
person.reset_country
}
}
}.open
# ... more code follows
Run (with the glimmer-dsl-tk gem installed):
ruby -r glimmer-dsl-tk -e "require '../samples/hello/hello_combo.rb'"
Glimmer app:
Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps)
Glimmer DSL for Opal is an experimental proof-of-concept web GUI adapter for Glimmer desktop apps (i.e. apps built with Glimmer DSL for SWT). It webifies them via Rails, allowing Ruby desktop apps to run on the web via Opal Ruby without changing a line of code. Apps may then be custom-styled for the web with standard CSS.
Glimmer DSL for Opal webifier successfully reuses the entire Glimmer core DSL engine in Opal Ruby inside a web browser, and as such inherits the full range of powerful Glimmer desktop data-binding capabilities for the web.
To get started, visit the Glimmer DSL for Opal project page for instructions on installing the glimmer-dsl-opal gem.
Glimmer DSL for Opal Samples
Hello, Computed!
Add the following require statement to app/assets/javascripts/application.rb
require 'samples/hello/hello_computed'
Or add the Glimmer code directly if you prefer to play around with it:
class HelloComputed
class Contact
attr_accessor :first_name, :last_name, :year_of_birth
def initialize(attribute_map)
@first_name = attribute_map[:first_name]
@last_name = attribute_map[:last_name]
@year_of_birth = attribute_map[:year_of_birth]
end
def name
"#{last_name}, #{first_name}"
end
def age
Time.now.year - year_of_birth.to_i
rescue
0
end
end
end
class HelloComputed
include Glimmer
def initialize
@contact = Contact.new(
first_name: 'Barry',
last_name: 'McKibbin',
year_of_birth: 1985
)
end
def launch
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
end
end
HelloComputed.new.launch
Glimmer app on the desktop (using glimmer-dsl-swt
gem):
Glimmer app on the web (using glimmer-dsl-opal
gem):
Start the Rails server:
rails s
Visit http://localhost:3000
You should see "Hello, Computed!"
Glimmer Calculator
Add the glimmer-cs-calculator gem to Gemfile
(without requiring):
gem 'glimmer-cs-calculator', require: false
Add the following require statement to app/assets/javascripts/application.rb
require 'glimmer-cs-calculator/launch'
Sample GUI code (relies on custom widgets command_button
, operation_button
, and number_button
):
shell {
minimum_size (OS.mac? ? 320 : (OS.windows? ? 390 : 520)), 240
image File.join(APP_ROOT, 'package', 'windows', "Glimmer Calculator.ico") if OS.windows?
text "Glimmer - Calculator"
grid_layout 4, true
# Setting styled_text to multi in order for alignment options to activate
styled_text(:multi, :wrap, :border) {
text bind(@presenter, :result)
alignment swt(:right)
right_margin 5
font height: 40
layout_data(:fill, :fill, true, true) {
horizontal_span 4
}
editable false
caret nil
}
('AC')
('÷')
('×')
('−')
(7..9).each { |number|
(number)
}
('+', font: @button_font_big, vertical_span: 2)
(4..6).each { |number|
(number)
}
(1..3).each { |number|
(number)
}
('=', font: @button_font_big, vertical_span: 2)
(0, horizontal_span: 2)
('.')
}
Glimmer app on the desktop (using the glimmer-dsl-swt
gem):
Glimmer app on the web (using glimmer-dsl-opal
gem):
Start the Rails server:
rails s
Visit http://localhost:3000
(or visit: http://glimmer-cs-calculator-server.herokuapp.com)
You should see "Glimmer Calculator"
Here is an Apple Calculator CSS themed version (with CSS only, no app code changes):
Visit http://glimmer-cs-calculator-server.herokuapp.com/welcomes/apple
You should see "Apple Calculator Theme"
Glimmer DSL for XML (& HTML)
Glimmer DSL for XML provides Ruby syntax for building XML (eXtensible Markup Language) documents.
Within the context of desktop development, Glimmer DSL for XML is useful in providing XML data for the SWT Browser widget.
XML DSL
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:
html
tag
: 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):
@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>
Glimmer DSL for CSS
Glimmer DSL for CSS provides Ruby syntax for building CSS (Cascading Style Sheets).
Within the context of Glimmer app development, Glimmer DSL for CSS is useful in providing CSS for the SWT Browser widget.
CSS DSL
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:
@css = css {
body {
font_size '1.1em'
pv 'background', 'white'
}
s('body > h1') {
background_color :red
pv 'font-size', '2em'
}
}
puts @css
Output:
body{font-size:1.1em;background:white}body > h1{background-color:red;font-size:2em}
DSL Engine
Glimmer is fundamentally a DSL Engine that can support any number of DSLs like the official Glimmer DSLs (gems starting with the glimmer-dsl-
prefix like glimmer-dsl-swt
) or any DSLs for that matter.
Glimmer DSL syntax consists mainly of:
- keywords (e.g.
table
for 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)
The Glimmer DSL Engine's architecture is based on the following Design Patterns and Data Structures:
- Interpreter Design Pattern: to define interpretable expressions of DSL keywords
- Chain of Responsibility Design Pattern / Queue Data Structure: to chain expression handlers in order of importance for processing DSL keywords
- Adapter Design Pattern: to adapt expressions into handlers in a chain of responsibility
- Stack Data Structure: to handle processing parent/child nesting of DSL keyword expressions in the correct order
Glimmer's use of the Interpreter Design Pattern in processing DSLs is also known as the Virtual Machine Architectural Style. After all, DSL expressions are virtual machine opcodes that process nested keywords stored in a stack. I built Glimmer's original DSL back in 2007 without knowing the Virtual Machine Architectural Style, but stumbled upon it anyways through following the Gang of Four Design Patterns mentioned above, chiefly the Interpreter Design Pattern.
Every keyword in a Glimmer DSL is represented by a DSL expression that is processed by an Expression
subclass selected from a chain of expressions (interpreters) pre-configured in a DSL chain of responsibility via Glimmer::DSL::Engine.add_dynamic_expressions(DSLNameModule, expression_names_array)
.
Expressions are either:
- Static (subclass of
StaticExpression
, which is a subclass ofExpression
): if they represent a single pre-identified keyword (e.g.color
ordisplay
) - Dynamic (subclass of
Expression
): if they represent keywords calculated on the fly during processing (e.g. an SWT widget likelabel
or a random XML element calledfolder
representing<folder></folder>
)
Optionally, expressions can be parent expressions that contain other expressions, and must include the ParentExpression
mixin module as such.
Additionally, every expression that serves as a top-level entry point into the DSL must mixin TopLevelExpression
Static expressions are optimized in performance since they pre-define methods on the Glimmer
module matching the static keywords they represent (e.g. color
causes creating a Glimmer#color
method for processing color
expressions) and completely bypass as a result the Glimmer DSL Engine Chain of Responsibility. That said, they must be avoided if the same keyword might occur multiple times, but with different requirements for arguments, block, and parenthood type.
Every Expression
sublcass must specify two methods at least:
can_interpret?(parent, keyword, *args, &block)
: to quickly test if the keyword and arg/block/parent combination qualifies for interpretation by the currentExpression
or to otherwise delegate to the next expression in the chain of responsibility.interpret(parent, keyword, *args, &block)
: to go ahead and interpret a DSL expression that qualified for interpretation
StaticExpression
sublcasses may skip the can_interpret?
method since they include a default implementation for it that matches the name of the keyword from the class name by convention. For example, a color
keyword would have a ColorExpression
class, so color
is inferred automatically from class name and used in deciding whether the class can handle a color
keyword or not.
ParentExpression
subclasses can optionally override this extra method, which is included by default and simply invokes the parent's passed block to process its children:
add_content(parent, &block)
For example, some parent widgets use their block for other reasons or process their children at very specific times, so they may override that method and disable it, or otherwise call super
and do additional work.
DSL expressions go into the glimmer/dsl/{dsl_name}
namespace directory.
Also, every DSL requires a glimmer/dsl/{dsl_name}/dsl.rb
file, which configures the DSL into Glimmer via a call to:
Glimmer::DSL::Engine.add_dynamic_expressions(DSLNameModule, expression_names_array)
Expression names are underscored verions of Expression
subclass names minus the _expression
suffix.
For example, here is an SWT DSL configuration:
require 'glimmer/launcher'
require Glimmer::Launcher.swt_jar_file
require 'glimmer/dsl/engine'
Dir[File.('../*_expression.rb', __FILE__)].each {|f| require f}
module Glimmer
module DSL
module SWT
Engine.add_dynamic_expressions(
SWT,
%w[
layout
widget_listener
combo_selection_data_binding
checkbox_group_selection_data_binding
radio_group_selection_data_binding
list_selection_data_binding
tree_items_data_binding
table_items_data_binding
data_binding
cursor
font
image
property
block_property
widget
custom_widget
]
)
end
end
end
In summary, these are the files needed to author a Glimmer DSL:
glimmer/dsl/[dsl_name]/dsl.rb
: requires and adds all dynamic expressions to [dsl_name] Glimmer DSLglimmer/dsl/[dsl_name]/[expresion_name]_expresion.rb
: needed for every [expresion_name] expression, whether dynamic or static
Multi-DSL Support
The Glimmer DSL Engine allows mixing DSLs, which comes in handy when doing things like using a desktop GUI DSL browser
widget with the HTML DSL and CSS DSL.
DSLs are activated by top-level keywords (expressions denoted as TopLevelExpression
). For example, the html
keyword activates the Glimmer DSL for XML. Glimmer automatically recognizes top-level keywords in each DSL and activates the DSL accordingly. Once done processing a nested DSL top-level keyword, Glimmer switches back to the prior DSL automatically.
Data-Binding Support
Data-Binding enables binding GUI properties (like text and color) to Model attributes (like name and age).
It relies on the Observer Design Pattern and MVP (Model-View-Presenter) Architectural Pattern (a variation on MVC)
These are the main classes concerning data-binding:
Observer
: Provides general observer support including unique registration and deregistration for cleanup and prevention of memory leaks. Main methods concerned are:call
,register
(alias:observe
), andunregister
(alias:unobserve
orderegister
)Observable
: General super-module for all observables. Main methods concerned are:add_observer
andremove_observer
ObservableModel
: Mixin module for any observable model with observable attributes. In addition toObservable
methods, it has anotify_observers
method to be called when changes occur. It automatically enhances all attribute setters (ending with=
) to notify observers on changes. Also, it automatically handles observing array attributes usingObservableArray
appropriately so they would notify observers upon array mutation changes.ObservableArray
: Mixin module for any observable array collection that automatically handles notifying observers upon performing array mutation operations (e.g.push
ordelete
)ModelBinding
: a higher-level abstraction that relies on all the other observer/observable classes to support basic data-binding, nested data-binding, and computed data-binding
Glimmer Process
Glimmer Process is the lightweight software development process used for building Glimmer libraries and Glimmer apps, which goes beyond Agile, rendering all Agile processes obsolete. Glimmer Process is simply made up of 7 guidelines to pick and choose as necessary until software development needs are satisfied.
Learn more by reading the GPG (Glimmer Process Guidelines)
Resources
- Code Master Blog
- JRuby Cookbook by Justin Edelson & Henry Liu
- MountainWest RubyConf 2011 Video
- RubyConf 2008 Video
- InfoQ Article
- DZone Tutorial
Help
Issues
You may submit issues on GitHub.
Click here to submit an issue.
Chat
Feature Suggestions
These features have been suggested. You might see them in a future version of Glimmer. You are welcome to contribute more feature suggestions.
Glimmer DSL Engine specific tasks are at:
Change Log
Contributing
Contributors Wanted!
If you would like to contribute to Glimmer, please study up on Glimmer and SWT, run all Glimmer samples, and build a small sample app (perhaps from this TODO list) to add to glimmer-dsl-swt Hello or Elaborate samples via a Pull Request. Once done, contact me on Chat.
You may apply for contributing to any of these Glimmer DSL gems whether you prefer to focus on the desktop or web:
- glimmer-dsl-swt: Glimmer DSL for SWT (JRuby Desktop Development GUI Framework)
- glimmer-dsl-tk: Glimmer DSL for Tk (Ruby Desktop Development GUI Library)
- glimmer-dsl-opal: Glimmer DSL for Opal (Web GUI Adapter for Desktop Apps)
- glimmer-dsl-xml: Glimmer DSL for XML (& HTML)
- glimmer-dsl-css: Glimmer DSL for CSS
Contributors
- Andy Maleh (Founder)
- Dennis Theisen (Contributor)
Click here to view contributor commits.
Hire Me
If your company would like to invest fulltime in further development of the Glimmer open-source project, hire me.
License
Copyright (c) 2007-2020 - Andy Maleh.
--
Glimmer logo was made by Freepik from www.flaticon.com