(Update 2022-09-02: certificate of merit & direct letter by Matz added)
I am very happy to announce that Glimmer DSL for LibUI (Ruby Desktop Development GUI Library) has been judged directly by Matz (creator of Ruby) and the 2022 Fukuoka Ruby Award Competition Judges and has won a
Fukuoka Ruby 2022 Special Award!
Glimmer DSL for LibUI is a prerequisite-free MRI Ruby desktop development GUI (Graphical User Interface) library. No need to pre-install any prerequisites. Just install the gem and have platform-independent native GUI that just works!
Mac | Windows | Linux |
---|---|---|
![]() |
![]() |
![]() |
LibUI is a thin Ruby wrapper around libui, a relatively new C GUI library that renders native controls on every platform (similar to SWT, but without the heavy weight of the Java Virtual Machine).
The main trade-off in using Glimmer DSL for LibUI as opposed to Glimmer DSL for SWT or Glimmer DSL for Tk is the fact that SWT and Tk are more mature than mid-alpha libui as GUI toolkits. Still, if there is only a need to build a small simple application, Glimmer DSL for LibUI could be a good convenient choice 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.
Glimmer DSL for LibUI 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 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
- Bidirectional/Unidirectional Data-Binding to declaratively wire and automatically synchronize GUI Views with Models
Hello, World!
require 'glimmer-dsl-libui'
include Glimmer
window('hello world').show
Mac | Windows | Linux |
---|---|---|
![]() |
![]() |
![]() |
Basic Table Progress Bar
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 |
---|---|---|
![]() |
![]() |
![]() |
Form Table
require 'glimmer-dsl-libui'
class FormTable
Contact = Struct.new(:name, :email, :phone, :city, :state)
include Glimmer
attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
def initialize
@contacts = [
Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
]
end
def launch
window('Contacts', 600, 600) {
margined true
vertical_box {
form {
stretchy false
entry {
label 'Name'
text <=> [self, :name] # bidirectional data-binding between entry text and self.name
}
entry {
label 'Email'
text <=> [self, :email]
}
entry {
label 'Phone'
text <=> [self, :phone]
}
entry {
label 'City'
text <=> [self, :city]
}
entry {
label 'State'
text <=> [self, :state]
}
}
button('Save Contact') {
stretchy false
on_clicked do
new_row = [name, email, phone, city, state]
if new_row.map(&:to_s).include?('')
msg_box_error('Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
else
@contacts << Contact.new(*new_row) # automatically inserts a row into the table due to explicit data-binding
@unfiltered_contacts = @contacts.dup
self.name = '' # automatically clears name entry through explicit data-binding
self.email = ''
self.phone = ''
self.city = ''
self.state = ''
end
end
}
search_entry {
stretchy false
# bidirectional data-binding of text to self.filter_value with after_write option
text <=> [self, :filter_value,
after_write: ->(filter_value) { # execute after write to self.filter_value
@unfiltered_contacts ||= @contacts.dup
# Unfilter first to remove any previous filters
self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
# Now, apply filter if entered
unless filter_value.empty?
self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
contact.members.any? do |attribute|
contact[attribute].to_s.downcase.include?(filter_value.downcase)
end
end
end
}
]
}
table {
text_column('Name')
text_column('Email')
text_column('Phone')
text_column('City')
text_column('State')
editable true
cell_rows <=> [self, :contacts] # explicit data-binding to self.contacts Modal Array, auto-inferring model attribute names from underscored table column names by convention
on_changed do |row, type, row_data|
puts "Row #{row} #{type}: #{row_data}"
$stdout.flush # for Windows
end
on_edited do |row, row_data| # only fires on direct table editing
puts "Row #{row} edited: #{row_data}"
$stdout.flush # for Windows
end
}
}
}.show
end
end
FormTable.new.launch
Mac | Windows | Linux |
---|---|---|
![]() |
![]() |
![]() |
Area Gallery
require 'glimmer-dsl-libui'
include Glimmer
window('Area Gallery', 400, 400) {
area {
path { # declarative stable path (explicit path syntax for multiple shapes sharing attributes)
square(0, 0, 100)
square(100, 100, 400)
fill r: 102, g: 102, b: 204
}
path { # declarative stable path (explicit path syntax for multiple shapes sharing attributes)
rectangle(0, 100, 100, 400)
rectangle(100, 0, 400, 100)
# linear gradient (has x0, y0, x1, y1, and stops)
fill x0: 10, y0: 10, x1: 350, y1: 350, stops: [{pos: 0.25, r: 204, g: 102, b: 204}, {pos: 0.75, r: 102, g: 102, b: 204}]
}
polygon(100, 100, 100, 400, 400, 100, 400, 400) { # declarative stable path (implicit path syntax for a single shape nested directly under area)
fill r: 202, g: 102, b: 104, a: 0.5
stroke r: 0, g: 0, b: 0
}
polybezier(0, 0,
200, 100, 100, 200, 400, 100,
300, 100, 100, 300, 100, 400,
100, 300, 300, 100, 400, 400) { # declarative stable path (implicit path syntax for a single shape nested directly under area)
fill r: 202, g: 102, b: 204, a: 0.5
stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
}
polyline(100, 100, 400, 100, 100, 400, 400, 400, 0, 0) { # declarative stable path (implicit path syntax for a single shape nested directly under area)
stroke r: 0, g: 0, b: 0, thickness: 2
}
arc(404, 216, 190, 90, 90, false) { # declarative stable path (implicit path syntax for a single shape nested directly under area)
# radial gradient (has an outer_radius in addition to x0, y0, x1, y1, and stops)
fill outer_radius: 90, x0: 0, y0: 0, x1: 500, y1: 500, stops: [{pos: 0.25, r: 102, g: 102, b: 204, a: 0.5}, {pos: 0.75, r: 204, g: 102, b: 204}]
stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
}
circle(200, 200, 90) { # declarative stable path (implicit path syntax for a single shape nested directly under area)
fill r: 202, g: 102, b: 204, a: 0.5
stroke r: 0, g: 0, b: 0, thickness: 2
}
text(161, 40, 100) { # declarative stable text
string('Area Gallery') {
font family: 'Arial', size: (OS.mac? ? 14 : 11)
color :black
}
}
on_mouse_event do |area_mouse_event|
p area_mouse_event
end
on_mouse_moved do |area_mouse_event|
puts 'moved'
end
on_mouse_down do |area_mouse_event|
puts 'mouse down'
end
on_mouse_up do |area_mouse_event|
puts 'mouse up'
end
on_mouse_drag_started do |area_mouse_event|
puts 'drag started'
end
on_mouse_dragged do |area_mouse_event|
puts 'dragged'
end
on_mouse_dropped do |area_mouse_event|
puts 'dropped'
end
on_mouse_entered do
puts 'entered'
end
on_mouse_exited do
puts 'exited'
end
on_key_event do |area_key_event|
p area_key_event
end
on_key_up do |area_key_event|
puts 'key up'
end
on_key_down do |area_key_event|
puts 'key down'
end
}
}.show
Mac | Windows | Linux |
---|---|---|
![]() |
![]() |
![]() |
1 comment:
You're doing a fantastic job of improving Ruby Desktop application efficiency; keep it up!. I'm glad you were able to overcome this adversity and emerge stronger than before; I value what you do for Ruby. Keep up the wonderful work.
Post a Comment