Written by {% avatar AndyObtiva %} Andy Maleh
Software Engineering Expert from Montreal, Quebec. Creator of Glimmer and Abstract Feature Branch. Speaker at RailsConf, RubyConf, AgileConf, EclipseCon, EclipseWorld. Master in Software Engineering, DePaul University, Chicago. Blogs at Code Mastery Takes Commitment To Bold Coding Adventures. Snowboarder and Drummer.
Glimmer DSL for LibUI is a prerequisite-free Ruby desktop development GUI (Graphical User Interface) library. LibUI is a thin Ruby wrapper around libui, a relatively new C GUI library that renders native desktop GUI controls on every platform (similar to SWT, but without the heavy weight of the Java Virtual Machine).
Glimmer DSL for LibUI is a good convenient choice for small simple applications due to having zero prerequisites beyond the dependencies included in the Ruby gem. Also, just like Glimmer DSL for Tk, its apps start instantly and have a small memory footprint. LibUI is a promising new GUI toolkit that might prove quite worthy in the future.
Mac | Windows | Linux |
---|---|---|
No need to pre-install any prerequisites. Just install the gem and have platform-independent native GUI that just works!
Glimmer DSL for LibUI aims to provide a DSL (Domain Specific Language) that enables more productive desktop development in Ruby with:
- Declarative DSL syntax that visually maps to the GUI control hierarchy
- Convention over configuration via smart defaults and automation of low-level details
- Requiring the least amount of syntax possible to build GUI
- Custom Keyword support
- Automates wiring of controls (e.g.
button
is automatically set as child ofwindow
) - Hides lower-level details (e.g.
LibUI.main
loop is started automatically when triggeringshow
onwindow
)
The Glimmer GUI DSL follows these simple concepts in mapping from LibUI syntax:
- Keyword(args): LibUI controls may be declared by lower-case underscored name (aka keyword) (e.g.
window
orbutton
). Behind the scenes, they are represented by keyword methods that map to correspondingLibUI.new_keyword
methods receiving args (e.g.window('hello world', 300, 200, true)
). - Content Block (Properties/Listeners/Controls): Any keyword may be optionally followed by a Ruby curly-brace multi-line content block containing properties (attributes), listeners, and/or nested controls (e.g.
window {title 'hello world'; on_closing {puts 'Bye'}; button('greet')}
). Content block optionally receives one arg representing the control (e.g.button('greet') {|b| on_clicked { puts b.text}}
) - Property: Control properties may be declared inside keyword blocks with lower-case underscored name followed by property value args (e.g.
title "hello world"
insidegroup
). Behind the scenes, properties correspond toLibUI.control_set_property
methods. - Listener: Control listeners may be declared inside keyword blocks with listener lower-case underscored name beginning with
on_
and receiving required block handler (e.g.on_clicked {puts 'clicked'}
insidebutton
). Optionally, the listener block can receive an arg representing the control (e.g.on_clicked {|btn| puts btn.text}
). Behind the scenes, listeners correspond toLibUI.control_on_event
methods.
Example of an app written in Glimmer GUI DSL object-oriented declarative hierarchical syntax:
require 'glimmer-dsl-libui'
include Glimmer
window('hello world', 300, 200) {
button('Button') {
on_clicked do
msg_box('Information', 'You clicked the button')
end
}
on_closing do
puts 'Bye Bye'
end
}.show
Mac Screenshots
Windows Screenshots
Linux Screenshots
require 'glimmer-dsl-libui'
include Glimmer
window('hello world').show
Mac Screenshot:
Windows Screenshot:
Linux Screenshot:
This is an example that demonstrates the table
control in Glimmer DSL for LibUI.
The table
cell_rows
property declaration results in "implicit data-binding" between the table
control and Array
of Arrays
; a new Glimmer DSL for LibUI innovation that provides convenience automatic support for:
- Deleting cell rows: Calling
Array#delete
,Array#delete_at
,Array#delete_if
, or any filtering/deletionArray
method automatically deletes rows in actualtable
control - Inserting cell rows: Calling
Array#<<
,Array#push
,Array#prepend
, or any insertion/additionArray
method automatically inserts rows in actualtable
control - Changing cell rows: Calling
Array#[]=
,Array#map!
, or any updateArray
method automatically updates rows in actualtable
control
require 'glimmer-dsl-libui'
include Glimmer
data = [
['task 1', 0],
['task 2', 15],
['task 3', 100],
['task 4', 75],
['task 5', -1],
]
window('Task Progress', 300, 200) {
vertical_box {
table {
text_column('Task')
progress_bar_column('Progress')
cell_rows data # implicit data-binding
}
button('Mark All As Done') {
stretchy false
on_clicked do
data.each_with_index do |row_data, row|
data[row][1] = 100 # automatically updates table due to implicit data-binding
end
end
}
}
}.show
Mac
Windows
Linux
Snake provides an example of building a desktop application test-first following the MVP (Model / View / Presenter) architectural pattern.
require 'glimmer-dsl-libui'
require 'glimmer/data_binding/observer'
require_relative 'snake/presenter/grid'
class Snake
CELL_SIZE = 15
SNAKE_MOVE_DELAY = 0.1
include Glimmer
def initialize
@game = Model::Game.new
@grid = Presenter::Grid.new(@game)
@game.start
create_gui
register_observers
end
def launch
@main_window.show
end
def register_observers
@game.height.times do |row|
@game.width.times do |column|
Glimmer::DataBinding::Observer.proc do |new_color|
@cell_grid[row][column].fill = new_color
end.observe(@grid.cells[row][column], :color)
end
end
Glimmer::DataBinding::Observer.proc do |game_over|
Glimmer::LibUI.queue_main do
if game_over
msg_box('Game Over!', "Score: #{@game.score}")
@game.start
end
end
end.observe(@game, :over)
Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
unless @game.over?
@game.snake.move
@main_window.title = "Glimmer Snake (Score: #{@game.score})"
end
end
end
def create_gui
@cell_grid = []
@main_window = window('Glimmer Snake', @game.width * CELL_SIZE, @game.height * CELL_SIZE) {
resizable false
vertical_box {
padded false
@game.height.times do |row|
@cell_grid << []
horizontal_box {
padded false
@game.width.times do |column|
area {
@cell_grid.last << path {
square(0, 0, CELL_SIZE)
fill Presenter::Cell::COLOR_CLEAR
}
on_key_up do |area_key_event|
orientation_and_key = [@game.snake.head.orientation, area_key_event[:ext_key]]
case orientation_and_key
in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
@game.snake.turn_right
in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
@game.snake.turn_left
else
# No Op
end
end
}
end
}
end
}
}
end
end
Snake.new.launch
Mac
Windows
Linux