Description
Glimmer DSL for LibUI is a dependency-free Ruby desktop development GUI library. No need to pre-install any pre-requisites. Just install the gem and have platform-independent GUI that just works!
The main trade-off against Glimmer DSL for SWT and Glimmer DSL for Tk is the fact that SWT and Tk are more mature than LibUI as GUI toolkits. Still, if there is only a need to build a small simple application, Glimmer DSL for LibUI could be a pretty good choice due to having zero external dependencies beyond installing the Ruby gem.
Glimmer DSL for LibUI alternatives and similar gems
Based on the "GUI" category.
Alternatively, view glimmer-dsl-libui alternatives based on common mentions on social networks and blogs.
-
Glimmer
DSL Framework consisting of a DSL Engine and a Data-Binding Library used in Glimmer DSL for SWT (JRuby Desktop Development GUI Framework), Glimmer DSL for Opal (Pure Ruby Web GUI), Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development GUI Library), Glimmer DSL for Tk (Ruby Tk Desktop Development GUI Library), Glimmer DSL for GTK (Ruby-GNOME Desktop Development GUI Library), Glimmer DSL for XML (& HTML), and Glimmer DSL for CSS -
Glimmer DSL for SWT
Glimmer DSL for SWT (JRuby Desktop Development Cross-Platform Native GUI Framework) - The Quickest Way From Zero To GUI - If You Liked Shoes, You'll Love Glimmer! -
Gladiator (Glimmer Editor)
Gladiator (short for Glimmer Editor) is a Glimmer DSL for SWT sample project under on-going development that demonstrates how to build a text editor in Ruby using Glimmer DSL for SWT (JRuby Desktop Development GUI Library). It is not intended to be a full-fledged editor by any means, yet mostly a fun educational exercise in using Glimmer. Gladiator is also a personal tool for shaping an editor exactly the way I like, with all the keyboard shortcuts I prefer. I leave building truly professional text editors to software tooling experts who would hopefully use Glimmer one day. Otherwise, I have been happily using Gladiator to develop all my open-source projects since May of 2020. -
Glimmer DSL for WX
Glimmer DSL for WX - Ruby Desktop Development GUI Library for the wxWidgets GUI toolkit and wxruby3 binding -
Glimmer DSL for Swing
Glimmer DSL for Swing (JRuby Swing Desktop Development GUI Library) - Enables development of desktop applications using Java Swing and Java 2D, including vector graphics and AWT geometry.
Scout Monitoring - Performance metrics and, now, Logs Management Monitoring with Scout Monitoring
* Code Quality Rankings and insights are calculated and provided by Lumnify.
They vary from L1 to L5 with "L5" being the highest.
Do you think we are missing an alternative of Glimmer DSL for LibUI or a related project?
README
Glimmer DSL for LibUI 0.5.24
Prerequisite-Free Ruby Desktop Development GUI Library
(Fukuoka Ruby Award Competition 2022 Special Award Winner [Award Announcement])
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 |
---|---|---|
[glimmer-dsl-libui-mac-control-gallery.png](images/glimmer-dsl-libui-mac-control-gallery.png) | [glimmer-dsl-libui-windows-control-gallery.png](images/glimmer-dsl-libui-windows-control-gallery.png) | [glimmer-dsl-libui-linux-control-gallery.png](images/glimmer-dsl-libui-linux-control-gallery.png) |
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 Control support
- Bidirectional/Unidirectional Data-Binding to declaratively wire and automatically synchronize GUI Views with Models
- [Far Future Plan] Scaffolding for new custom controls, apps, and gems
- [Far Future Plan] Native-Executable packaging on Mac, Windows, and Linux.
Hello, World!
require 'glimmer-dsl-libui'
include Glimmer
window('hello world').show
Mac | Windows | Linux |
---|---|---|
[glimmer-dsl-libui-mac-basic-window.png](images/glimmer-dsl-libui-mac-basic-window.png) | [glimmer-dsl-libui-windows-basic-window.png](images/glimmer-dsl-libui-windows-basic-window.png) | [glimmer-dsl-libui-linux-basic-window.png](images/glimmer-dsl-libui-linux-basic-window.png) |
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 |
---|---|---|
[glimmer-dsl-libui-mac-basic-table-progress-bar.png](images/glimmer-dsl-libui-mac-basic-table-progress-bar.png) | [glimmer-dsl-libui-windows-basic-table-progress-bar.png](images/glimmer-dsl-libui-windows-basic-table-progress-bar.png) | [glimmer-dsl-libui-linux-basic-table-progress-bar.png](images/glimmer-dsl-libui-linux-basic-table-progress-bar.png) |
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', '[email protected]', '720-523-4329', 'Denver', 'CO'),
Contact.new('Jordan Biggins', '[email protected]', '617-528-5399', 'Boston', 'MA'),
Contact.new('Mary Glass', '[email protected]', '847-589-8788', 'Elk Grove Village', 'IL'),
Contact.new('Darren McGrath', '[email protected]', '206-539-9283', 'Seattle', 'WA'),
Contact.new('Melody Hanheimer', '[email protected]', '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 |
---|---|---|
[glimmer-dsl-libui-mac-form-table.png](images/glimmer-dsl-libui-mac-form-table.png) | [glimmer-dsl-libui-windows-form-table.png](images/glimmer-dsl-libui-windows-form-table.png) | [glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png) |
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 |
---|---|---|
[glimmer-dsl-libui-mac-area-gallery.png](images/glimmer-dsl-libui-mac-area-gallery.png) | [glimmer-dsl-libui-windows-area-gallery.png](images/glimmer-dsl-libui-windows-area-gallery.png) | [glimmer-dsl-libui-linux-area-gallery.png](images/glimmer-dsl-libui-linux-area-gallery.png) |
Check Out Many More Examples Over Here!
[glimmer-dsl-libui-mac-snake.gif](images/glimmer-dsl-libui-mac-snake.gif)
[glimmer-dsl-libui-mac-color-the-circles.gif](images/glimmer-dsl-libui-mac-color-the-circles.gif)
[glimmer-dsl-libui-mac-tetris.gif](images/glimmer-dsl-libui-mac-tetris.gif)
NOTE: Glimmer DSL for LibUI is 100% feature-complete with regards to covering the C libui library API and in beta mode (though the C libui is still mid-alpha, which is why Glimmer DSL for LibUI cannot be declared v1.0.0 yet). Please help make better by contributing, adopting for small or low risk projects, and providing feedback. The more feedback and issues you report the better.
Glimmer DSL Comparison Table: DSL | Platforms | Native? | Vector Graphics? | Pros | Cons | Prereqs ----|-----------|---------|------------------|------|------|-------- Glimmer DSL for SWT (JRuby Desktop Development GUI Framework) | Mac / Windows / Linux | Yes | Yes (Canvas Shape DSL) | Very Mature / Scaffolding / Native Executable Packaging / Custom Widgets | Slow JRuby Startup Time / Heavy Memory Footprint | Java / JRuby Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps) | All Web Browsers | No | Yes (Canvas Shape DSL) | Simpler than All JavaScript Technologies / Auto-Webify Desktop Apps | Setup Process / Only Rails 5 Support for Now | Rails Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development GUI Library) | Mac / Windows / Linux | Yes | Yes (Area API) | Fast Startup Time / Light Memory Footprint | LibUI is an Incomplete Mid-Alpha Only | None Other Than MRI Ruby Glimmer DSL for Tk (Ruby Tk Desktop Development GUI Library) | Mac / Windows / Linux | Some Native-Themed Widgets (Not Truly Native) | Yes (Canvas) | Fast Startup Time / Light Memory Footprint | Complicated Setup / Widgets Do Not Look Truly Native, Espcially on Linux | ActiveTcl / MRI Ruby Glimmer DSL for GTK (Ruby-GNOME Desktop Development GUI Library) | Mac / Windows / Linux | Only on Linux | Yes (Cairo) | Complete Access to GNOME Features on Linux (Forte) | Not Native on Mac and Windows | None Other Than MRI Ruby on Linux / Brew Packages on Mac / MSYS & MING Toolchains on Windows / MRI Ruby Glimmer DSL for FX (FOX Toolkit Ruby Desktop Development GUI Library) | Mac (requires XQuartz) / Windows / Linux | No | Yes (Canvas) | No Prerequisites on Windows (Forte Since Binaries Are Included Out of The Box) | Widgets Do Not Look Native / Mac Usage Obtrusively Starts XQuartz | None Other Than MRI Ruby on Windows / XQuarts on Mac / MRI Ruby Glimmer DSL for JFX (JRuby JavaFX Desktop Development GUI Library) | Mac / Windows / Linux | No | Yes (javafx.scene.shape and javafx.scene.canvas) | Rich in Custom Widgets | Slow JRuby Startup Time / Heavy Memory Footprint / Widgets Do Not Look Native | Java / JRuby / JavaFX SDK Glimmer DSL for Swing (JRuby Swing Desktop Development GUI Library) | Mac / Windows / Linux | No | Yes (Java2D) | Very Mature | Slow JRuby Startup Time / Heavy Memory Footprint / Widgets Do Not Look Native | Java / JRuby Glimmer DSL for XML (& HTML) | All Web Browsers | No | Yes (SVG) | Programmable / Lighter-weight Than Actual XML | XML Elements Are Sometimes Not Well-Named (Many Types of Input) | None Glimmer DSL for CSS | All Web Browsers | No | Yes | Programmable | CSS Is Over-Engineered / Too Many Features To Learn | None
Table of Contents
- [Glimmer DSL for LibUI](#)
- Glimmer GUI DSL Concepts
- Usage
- Girb (Glimmer IRB)
- API
- Supported Keywords
- Common Control Properties
- Common Control Operations
- LibUI Operations
- Extra Dialogs
- Extra Operations
- Table API
- Area API
- Smart Defaults and Conventions
- Custom Keywords
- Observer Pattern
- Data-Binding
- API Gotchas
- Original API
- Packaging
- Glimmer Style Guide
- Examples
- Basic Examples
- Advanced Examples
- Applications
- Manga2PDF
- Befunge98 GUI
- i3off Gtk Ruby
- Chess
- RubyCrumbler
- Rubio-Radio
- PMV Calc
- Process
- Resources
- Help
- Issues
- Chat
- Planned Features and Feature Suggestions
- Change Log
- Contributing
- Contributors
- License
Glimmer GUI DSL Concepts
The Glimmer GUI DSL provides object-oriented declarative hierarchical syntax for LibUI that:
- Supports smart defaults (e.g. automatic
on_closing
listener that quitswindow
) - 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
) - Nests controls 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 LibUI syntax:
Keyword(args): LibUI controls may be declared by lower-case underscored name (aka keyword from list of supported keywords) (e.g. window
or button
). Behind the scenes, they are represented by keyword methods that map to corresponding LibUI.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.
Example:
window {
title 'hello world' # property
on_closing do # listener (always has a do; end block to signify logic)
puts 'Bye'
end
button('greet') { # nested control
on_clicked do
puts 'hello world'
end
}
}
Content block optionally receives one arg representing the controll
Example:
button('greet') { |b|
on_clicked do
puts b.text
end
}
Property: Control properties may be declared inside keyword blocks with lower-case underscored name followed by property value args (e.g. title "hello world"
inside group
). Behind the scenes, properties correspond to LibUI.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 (always followed by a do; end
style block to signify logic).
Example:
button('click') {
on_clicked do
puts 'clicked'
end
}
Optionally, the listener block can receive an arg representing the control.
button('click') {
on_clicked do |btn|
puts btn.text
end
}
Behind the scenes, listeners correspond to LibUI.control_on_event
methods.
Method: Controls have methods that invoke certain operations on them. For example, window
has a #show
method that shows the window GUI. More methods are mentioned under API
Example of an app written in LibUI's procedural imperative syntax:
require 'libui'
UI = LibUI
UI.init
main_window = UI.new_window('hello world', 300, 200, 1)
button = UI.new_button('Button')
UI.button_on_clicked(button) do
UI.msg_box(main_window, 'Information', 'You clicked the button')
end
UI.window_on_closing(main_window) do
puts 'Bye Bye'
UI.control_destroy(main_window)
UI.quit
0
end
UI.window_set_child(main_window, button)
UI.control_show(main_window)
UI.main
UI.quit
Example of the same app written in Glimmer 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
Usage
Install glimmer-dsl-libui gem directly into a maintained Ruby version:
gem install glimmer-dsl-libui
Or install via Bundler Gemfile
:
gem 'glimmer-dsl-libui', '~> 0.5.24'
Test that installation worked by running the Meta-Example:
ruby -r glimmer-dsl-libui -e "require 'examples/meta_example'"
Mac | Windows | Linux |
---|---|---|
[glimmer-dsl-libui-mac-meta-example.png](images/glimmer-dsl-libui-mac-meta-example.png) | [glimmer-dsl-libui-windows-meta-example.png](images/glimmer-dsl-libui-windows-meta-example.png) | [glimmer-dsl-libui-linux-meta-example.png](images/glimmer-dsl-libui-linux-meta-example.png) |
Now to use glimmer-dsl-libui, add require 'glimmer-dsl-libui'
at the top.
Afterwards, include Glimmer
into the top-level main object for testing or into an actual class for serious usage.
Alternatively, include Glimmer::LibUI::Application
to conveniently declare the GUI body
and run via the ::launch
method (Glimmer::LibUI::Application
is an alias for Glimmer::LibUI::CustomWindow
since that is what it represents).
Example including Glimmer::LibUI::Application
(you may copy/paste in girb
):
require 'glimmer-dsl-libui'
class SomeGlimmerApp
include Glimmer::LibUI::Application
body {
window('hello world', 300, 200) {
button('Button') {
on_clicked do
puts 'Button Clicked'
end
}
}
}
end
SomeGlimmerApp.launch
Example including Glimmer
and manually implementing the #launch
method (you may copy/paste in girb
):
require 'glimmer-dsl-libui'
class SomeGlimmerApp
include Glimmer
def launch
window('hello world', 300, 200) {
button('Button') {
on_clicked do
puts 'Button Clicked'
end
}
}.show
end
end
SomeGlimmerApp.new.launch
Example including Glimmer
at the top-level scope just for some prototyping/demoing/testing (you may copy/paste in girb
):
require 'glimmer-dsl-libui'
include Glimmer
window('hello world', 300, 200) {
button('Button') {
on_clicked do
puts 'Button Clicked'
end
}
}.show
If you are new to Glimmer DSL for LibUI, check out Girb and Examples to quickly learn through copy/paste. You may refer to the API later on once you have gotten your feet wet with Glimmer DSL for LibUI and need more detailed reference information.
Girb (Glimmer IRB)
You can run the girb
command (bin/girb
if you cloned the project locally) to do some quick and dirty experimentation and learning:
girb
This gives you irb
with the glimmer-dsl-libui
gem loaded and the Glimmer
module mixed into the main object for easy experimentation with GUI.
[glimmer-dsl-libui-girb.png](images/glimmer-dsl-libui-girb.png)
For a more advanced code editing tool, check out the Meta-Example (The Example of Examples).
Gotcha: On the Mac, when you close a window opened in girb
, it remains open until you enter exit
or open another GUI window.
API
Any control returned by a Glimmer GUI DSL keyword declaration can be introspected for its properties and updated via object-oriented attributes (standard Ruby attr
/attr=
or set_attr
).
Example (you may copy/paste in girb
):
w = window('hello world')
puts w.title # => hello world
w.title = 'howdy'
puts w.title # => howdy
w.set_title 'aloha'
puts w.title # => aloha
Controls are wrapped as Ruby proxy objects, having a #libui
method to obtain the wrapped LibUI Fiddle pointer object. Ruby proxy objects rely on composition (via Proxy Design Pattern) instead of inheritance to shield consumers from having to deal with lower-level details unless absolutely needed. That said, you can invoke any LibUI operation on the Glimmer proxy object directly and it gets proxied automatically to the wrapped Fiddle pointer object (e.g. window_proxy.title
gets proxied to LibUI.window_title(window_proxy.libui).to_s
automatically), so you rarely have to refer to the wrapped #libui
Fiddle pointer object directly.
Example (you may copy/paste in girb
):
w = window('hello world') # => #<Glimmer::LibUI::WindowProxy:0x00007fde4ea39fb0
w.libui # => #<Fiddle::Pointer:0x00007fde53997980 ptr=0x00007fde51352a60 size=0 free=0x0000000000000000>
w.title == LibUI.window_title(w.libui).to_s # => true
Supported Keywords
These are all the supported keywords. Note that some keywords do not represent controls. For example, some keywords produce objects that are used as the property values of controls (e.g. image
can be used as a control under area
or alternatively build objects to use in cell_rows
for a table
with an image_column
)
Keyword(Args) | Properties | Listeners |
---|---|---|
about_menu_item |
None | on_clicked |
area |
auto_draw_enabled |
on_draw(area_draw_params) , on_mouse_event(area_mouse_event) , on_mouse_down(area_mouse_event) , on_mouse_up(area_mouse_event) , on_mouse_drag_started(area_mouse_event) , on_mouse_dragged(area_mouse_event) , on_mouse_dropped(area_mouse_event) , on_mouse_entered , on_mouse_exited , on_key_event(area_key_event) , on_key_down(area_key_event) , on_key_up(area_key_event) |
arc(x_center as Numeric, y_center as Numeric, radius as Numeric, start_angle as Numeric, sweep as Numeric, is_negative as Boolean) |
x_center (Numeric ), y_center (Numeric ), radius (Numeric ), start_angle (Numeric ), sweep (Numeric ), is_negative (Boolean) |
None |
background_color_column |
None | None |
bezier(x = nil as Numeric, y = nil as Numeric, c1_x as Numeric, c1_y as Numeric, c2_x as Numeric, c2_y as Numeric, end_x as Numeric, end_y as Numeric) |
x (Numeric ), y (Numeric ), c1_x (Numeric ), c1_y (Numeric ), c2_x (Numeric ), c2_y (Numeric ), end_x (Numeric ), end_y (Numeric ) |
None |
button(text as String) |
text (String ) |
on_clicked |
button_column(name as String) |
enabled (Boolean) |
None |
checkbox(text as String) |
checked (Boolean), text (String ) |
on_toggled |
checkbox_column(name as String) |
editable (Boolean) |
None |
checkbox_text_column(name as String) |
editable (Boolean), editable_checkbox (Boolean), editable_text (Boolean) |
None |
checkbox_text_color_column(name as String) |
editable (Boolean), editable_checkbox (Boolean), editable_text (Boolean) |
None |
check_menu_item(text as String) |
checked (Boolean) |
on_clicked |
code_area |
language (String) (default: 'ruby' ), theme (String) (default: 'glimmer' ), code (String) |
None |
combobox |
items (Array of String ), selected (Integer ), selected_item (String ) |
on_selected |
color_button |
color (Array of red as Float , green as Float , blue as Float , alpha as Float ), red as Float , green as Float , blue as Float , alpha as Float |
on_changed |
date_picker |
time (Hash of keys: sec as Integer , min as Integer , hour as Integer , mday as Integer , mon as Integer , year as Integer , wday as Integer , yday as Integer , dst as Boolean) |
on_changed |
date_time_picker |
time (Hash of keys: sec as Integer , min as Integer , hour as Integer , mday as Integer , mon as Integer , year as Integer , wday as Integer , yday as Integer , dst as Boolean) |
on_changed |
editable_combobox |
items (Array of String ), text (String ) |
on_changed |
entry |
read_only (Boolean), text (String ) |
on_changed |
figure(x=nil as Numeric, y=nil as Numeric) |
x (Numeric ), y (Numeric ), closed (Boolean) |
None |
font_button |
font [read-only] (Hash of keys: :family , :size , :weight , :italic , :stretch ), family as String , size as Float , weight as Integer , italic as Integer , stretch as Integer |
on_changed |
form |
padded (Boolean) |
None |
grid |
padded (Boolean) |
None |
group(text as String) |
margined (Boolean), title (String ) |
None |
horizontal_box |
padded (Boolean) |
None |
horizontal_separator |
None | None |
image(file as String = nil, width as Numeric = nil, height as Numeric = nil) |
file (String path or URL), width , height |
None |
image_part(pixels as String [encoded image rgba byte array], width as Numeric, height as Numeric, byte_stride as Numeric [usually width*4]) |
None | None |
image_column(name as String) |
None | None |
image_text_column(name as String) |
None | None |
image_text_color_column(name as String) |
None | None |
label(text as String) |
text (String ) |
None |
line(x as Numeric, y as Numeric, end_x = nil as Numeric, end_y = nil as Numeric) |
x (Numeric ), y (Numeric ), end_x (Numeric ), end_y (Numeric ) |
None |
matrix(m11 = nil as Numeric, m12 = nil as Numeric, m21 = nil as Numeric, m22 = nil as Numeric, m31 = nil as Numeric, m32 = nil as Numeric) |
m11 (Numeric ), m12 (Numeric ), m21 (Numeric ), m22 (Numeric ), m31 (Numeric ), m32 (Numeric ) |
None |
menu(text as String) |
None | None |
menu_item(text as String) |
None | on_clicked |
message_box (alias for msg_box ; see for arguments) |
None | None |
message_box_error (alias for msg_box_error ; see for arguments) |
None | None |
multiline_entry |
read_only (Boolean), text (String ) |
on_changed |
msg_box(window = main_window as Glimmer::LibUI::WindowProxy, title as String, description as String) |
None | None |
msg_box_error(window = main_window as Glimmer::LibUI::WindowProxy, title as String, description as String) |
None | None |
non_wrapping_multiline_entry |
read_only (Boolean), text (String ) |
on_changed |
observe(model, property = nil) |
None | None |
password_entry |
read_only (Boolean), text (String ) |
on_changed |
path(draw_fill_mode = :winding) |
fill (Hash of :r as 0 -255 , :g as 0 -255 , :b as 0 -255 , :a as 0.0 -1.0 , hex, or X11 color), stroke (Hash of :r as 0 -255 , :g as 0 -255 , :b as 0 -255 , :a as 0.0 -1.0 , hex, or X11 color), :cap as (:round , :square , :flat ), :join as (:miter , :round , :bevel ), :thickness as Numeric , :miter_limit as Numeric , :dashes as Array of Numeric ) |
None |
polygon(point_array as Array of Arrays of Numeric or Array of Numeric) |
point_array (Array of Arrays of Numeric or Array of Numeric ) |
None |
polyline(point_array as Array of Arrays of Numeric or Array of Numeric) |
point_array (Array of Arrays of Numeric or Array of Numeric ) |
None |
polybezier(point_array as Array of Arrays of Numeric or Array of Numeric) |
point_array (Array of Arrays of Numeric or Array of Numeric ) |
None |
preferences_menu_item |
None | on_clicked |
progress_bar |
value (Numeric ) |
None |
progress_bar_column(name as String) |
None | None |
quit_menu_item |
None | on_clicked |
radio_buttons |
selected (Integer ) |
on_selected |
rectangle(x as Numeric, y as Numeric, width as Numeric, height as Numeric) |
x (Numeric ), y (Numeric ), width (Numeric ), height (Numeric ) |
None |
refined_table |
model_array (Array ), table_columns (Hash ), table_editable (Boolean), per_page (Integer ), page (Integer ), visible_page_count (Boolean), filter_query (String ), filter (Lambda) |
(EARLY ALPHA UNSTABLE API / CHECK SOURCE CODE FOR DETAILS) |
scrolling_area(width = main_window.width, height = main_window.height) |
auto_draw_enabled (Boolean), size (Array of width (Numeric ) and height (Numeric )), width (Numeric ), height (Numeric ) |
on_draw(area_draw_params) , on_mouse_event(area_mouse_event) , on_mouse_down(area_mouse_event) , on_mouse_up(area_mouse_event) , on_mouse_drag_started(area_mouse_event) , on_mouse_dragged(area_mouse_event) , on_mouse_dropped(area_mouse_event) , on_mouse_entered , on_mouse_exited , on_key_event(area_key_event) , on_key_down(area_key_event) , on_key_up(area_key_event) |
search_entry |
read_only (Boolean), text (String ) |
on_changed |
separator_menu_item |
None | None |
slider(min as Numeric, max as Numeric) |
value (Numeric ) |
on_changed |
spinbox(min as Numeric, max as Numeric) |
value (Numeric ) |
on_changed |
square(x as Numeric, y as Numeric, length as Numeric) |
x (Numeric ), y (Numeric ), length (Numeric ) |
None |
string(string = '') |
font , color (Hash of :r as 0 -255 , :g as 0 -255 , :b as 0 -255 , :a as 0.0 -1.0 , hex, or X11 color), background (Hash of :r as 0 -255 , :g as 0 -255 , :b as 0 -255 , :a as 0.0 -1.0 , hex, or X11 color), underline , underline_color (Hash of :r as 0 -255 , :g as 0 -255 , :b as 0 -255 , :a as 0.0 -1.0 , hex, or X11 color), open_type_features , string (String ) |
None |
tab |
margined (Boolean), num_pages (Integer ) |
None |
tab_item(name as String) |
index [read-only] (Integer ), margined (Boolean), name [read-only] (String ) |
None |
table |
cell_rows (Array (rows) of Arrays (row columns) of cell values (e.g. String values for text_column cells or Array of image /String for image_text_column )), editable as Boolean |
`on_changed { |
text(x = 0 as Numeric, y = 0 as Numeric, width = area_width as Numeric) |
align , default_font |
None |
text_column(name as String) |
editable (Boolean) |
None |
text_color_column(name as String) |
editable (Boolean) |
None |
time_picker |
time (Hash of keys: sec as Integer , min as Integer , hour as Integer ) |
on_changed |
vertical_box |
padded (Boolean) |
None |
vertical_separator |
None | None |
window(title as String, width as Integer, height as Integer, has_menubar as Boolean) |
borderless (Boolean), content_size (width Numeric , height Numeric ), width (Numeric ), height (Numeric ), fullscreen (Boolean), margined (Boolean), title (String ), resizable (Boolean) |
on_closing , on_content_size_changed , on_destroy |
Common Control Properties
enabled
(Boolean)libui
(Fiddle::Pointer
): returns wrapped LibUI objectparent_proxy
(Glimmer::LibUI::ControlProxy
or subclass)parent
(Fiddle::Pointer
)toplevel
[read-only] (Boolean)visible
(Boolean)stretchy
[dsl-only] (Boolean) [default=true
]: available in Glimmer GUI DSL when nested underhorizontal_box
,vertical_box
, orform
left
[dsl-only] (Integer
) [default=0
]: available in Glimmer GUI DSL when nested undergrid
top
[dsl-only] (Integer
) [default=0
]: available in Glimmer GUI DSL when nested undergrid
xspan
[dsl-only] (Integer
) [default=1
]: available in Glimmer GUI DSL when nested undergrid
yspan
[dsl-only] (Integer
) [default=1
]: available in Glimmer GUI DSL when nested undergrid
hexpand
[dsl-only] (Boolean) [default=false
]: available in Glimmer GUI DSL when nested undergrid
halign
[dsl-only] (:fill
,:start
,:center
, or:end
) [default=:fill
]: available in Glimmer GUI DSL when nested undergrid
vexpand
[dsl-only] (Boolean) [default=false
]: available in Glimmer GUI DSL when nested undergrid
valign
[dsl-only] (:fill
,:start
,:center
, or:end
) [default=:fill
]: available in Glimmer GUI DSL when nested undergrid
Common Control Operations
destroy
disable
enable
hide
show
LibUI Operations
All operations that could normally be called on LibUI
can also be called on Glimmer::LibUI
, but some have enhancements as detailed below.
Glimmer::LibUI::queue_main(&block)
: queues an operation to be run on the main event loop at the earliest opportunity possibleGlimmer::LibUI::timer(time_in_seconds=0.1, repeat: true, &block)
: calls block after time_in_seconds has elapsed, repeating indefinitely unless repeat isfalse
or anInteger
for finite number of repeats. Block can returnfalse
ortrue
to override next repetition.
There are additional useful Glimmer::LibUI
operations that are not found in LibUI
, which mostly help if you would like to do advanced lower level LibUI programming:
Glimmer::LibUI::integer_to_boolean(int, allow_nil: true)
Glimmer::LibUI::boolean_to_integer(int, allow_nil: true)
Glimmer::LibUI::degrees_to_radians(degrees)
Glimmer::LibUI::interpret_color(value)
: interprets a color in any form likeString
,Symbol
, or hex into an rgbHash
(including0x1f3b5d
,'0x1f3b5d'
,'#1f3b5d'
, and 3-char hex-shorthand variations)Glimmer::LibUI::hex_to_rgb(value)
: converts a hex color to an rgbHash
(including0x1f3b5d
,'0x1f3b5d'
,'#1f3b5d'
, and 3-char hex-shorthand variations)Glimmer::LibUI::enum_names
: provides all possible enum names to use withGlimmer::LibUI::enum_symbols(enum_name)
Glimmer::LibUI::enum_symbols(enum_name)
: returns all possible values for an enum.enum_name
can be::draw_brush_type
:[:solid, :linear_gradient, :radial_gradient, :image]
:draw_line_cap
:[:flat, :round, :square]
:draw_line_join
:[:miter, :round, :bevel]
:draw_fill_mode
:[:winding, :alternate]
:attribute_type
: attributes for attributedstring
s:[:family, :size, weight, :italic, :stretch, :color, :background, :underline, :underline_color, :features]
:text_weight
:[:minimum, :thin, :ultra_light, :light, :book, :normal, :medium, :semi_bold, :bold, :ultra_bold, :heavy, :ultra_heavy, :maximum]
:text_italic
:[:normal, :oblique, :italic]
:text_stretch
:[:ultra_condensed, :extra_condensed, :condensed, :semi_condensed, :normal, :semi_expanded, :expanded, :extra_expanded, :ultra_expanded]
:underline
:[:none, :single, :double, :suggestion, :color_custom, :color_spelling, :color_grammar, :color_auxiliary]
:underline_color
:[:custom, :spelling, :grammar, :auxiliary]
:draw_text_align
:[:left, :center, :right]
:modifier
:[:ctrl, :alt, :shift, :super]
:ext_key
:[:escape, :insert, :delete, :home, :end, :page_up, :page_down, :up, :down, :left, :right, :f1, :f2, :f3, :f4, :f5, :f6, :f7, :f8, :f9, :f10, :f11, :f12, :n0, :n1, :n2, :n3, :n4, :n5, :n6, :n7, :n8, :n9, :n_dot, :n_enter, :n_add, :n_subtract, :n_multiply, :n_divide]
:at
: for insertinggrid
controls:[:leading, :top, :trailing, :bottom]
:align
:[:fill, :start, :center, :end]
:table_value_type
:[:string, :image, :int, :color]
:table_model_column
:[:never_editable, :always_editable]
Glimmer::LibUI::enum_symbol_to_value(enum_name, enum_symbol, default_symbol: nil, default_index: 0)
Glimmer::LibUI::enum_value_to_symbol(enum_name, enum_value)
Glimmer::LibUI::x11_colors
: returns all X11 colors:[:alice_blue, :antique_white, :aqua, :aquamarine, :azure, :beige, :bisque, :rebecca_purple, :becca_purple, :blanched_almond, :blue, :blue_violet, :brown, :burly_wood, :burlywood, :cadet_blue, :carnation, :cayenne, :chartreuse, :chocolate, :coral, :cornflower_blue, :cornsilk, :crimson, :cyan, :dark_blue, :dark_cyan, :dark_golden_rod, :dark_goldenrod, :dark_gray, :dark_grey, :dark_green, :dark_khaki, :dark_magenta, :dark_olive_green, :darkolive_green, :dark_orange, :dark_orchid, :dark_red, :dark_salmon, :darksalmon, :dark_sea_green, :dark_slate_blue, :dark_slate_gray, :dark_slate_grey, :dark_turquoise, :dark_violet, :darkorange, :deep_pink, :deep_sky_blue, :dim_gray, :dim_grey, :dodger_blue, :feldspar, :fire_brick, :firebrick, :floral_white, :forest_green, :fuchsia, :gainsboro, :ghost_white, :gold, :golden_rod, :goldenrod, :gray, :grey, :gray10, :grey10, :gray20, :grey20, :gray30, :grey30, :gray40, :grey40, :gray50, :grey50, :gray60, :grey60, :gray70, :grey70, :gray80, :grey80, :gray90, :grey90, :green, :green_yellow, :honey_dew, :honeydew, :hot_pink, :indian_red, :indigo, :ivory, :khaki, :lavender, :lavender_blush, :lawn_green, :lemon_chiffon, :light_blue, :light_coral, :light_cyan, :light_golden_rod_yellow, :light_goldenrod_yellow, :light_gray, :light_grey, :light_green, :light_pink, :light_salmon, :lightsalmon, :light_sea_green, :light_sky_blue, :light_slate_blue, :light_slate_gray, :light_slate_grey, :light_steel_blue, :lightsteel_blue, :light_yellow, :lime, :lime_green, :linen, :magenta, :maroon, :medium_aqua_marine, :medium_aquamarine, :medium_blue, :medium_orchid, :medium_purple, :medium_sea_green, :medium_slate_blue, :medium_spring_green, :medium_turquoise, :medium_violet_red, :midnight_blue, :mint_cream, :misty_rose, :moccasin, :navajo_white, :navy, :old_lace, :olive, :olive_drab, :olivedrab, :orange, :orange_red, :orchid, :pale_golden_rod, :pale_goldenrod, :pale_green, :pale_turquoise, :pale_violet_red, :papaya_whip, :peach_puff, :peachpuff, :peru, :pink, :plum, :powder_blue, :purple, :red, :rosy_brown, :royal_blue, :saddle_brown, :salmon, :sandy_brown, :sea_green, :sea_shell, :seashell, :sienna, :silver, :sky_blue, :slate_blue, :slate_gray, :slate_grey, :snow, :spring_green, :steel_blue, :tan, :teal, :thistle, :tomato, :turquoise, :violet, :violet_red, :wheat, :white_smoke, :yellow, :yellow_green, :metallic, :white, :black, :gray_scale, :grey_scale]
Extra Dialogs
open_file(window as Glimmer::LibUI::WindowProxy = ControlProxy::main_window_proxy)
: returns selected file (String
) ornil
if cancelledsave_file(window as Glimmer::LibUI::WindowProxy = ControlProxy::main_window_proxy)
: returns selected file (String
) ornil
if cancelled
Extra Operations
ControlProxy::control_proxies
: returns all instantiated control proxies in the applicationControlProxy::menu_proxies
: returns all instantiatedmenu
proxies in the applicationControlProxy::image_proxies
: returns all instantiatedimage
proxies in the applicationControlProxy::main_window_proxy
: returns the first window proxy instantiated in the applicationControlProxy#window_proxy
: returns the window proxy parent for a controlControlProxy#content {...}
: re-opens control's content to add more nested controls or properties
Table API
The table
control must first declare its columns via one of these column keywords (mentioned in Supported Keywords):
background_color_column
: expects color cell valuesbutton_column
: expectsString
cell valuescheckbox_column
: expects Boolean cell valuescheckbox_text_column
: expects dual-elementArray
of Boolean andString
cell valuescheckbox_text_color_column
: expects triple-elementArray
of Boolean,String
, and color cell valuesimage_column
: expectsimage
cell values (produced byimage
andimage_part
keywords as per Supported Keywords)image_text_column
: expects dual-elementArray
ofimage
andString
cell valuesimage_text_color_column
: expects triple-elementArray
ofimage
,String
, and color cell valuestext_column
: expectsString
cell valuestext_color_column
: expects dual-elementArray
ofString
and color cell valuesprogress_bar_column
: expectsInteger
cell values
Afterwards, it must declare its cell_rows
array (Array
of Array
s of column cell values) and whether it is editable
(Boolean) for all its columns.
Note that the cell_rows
property declaration results in "implicit data-binding" between the table
control and Array
of Arrays
(a new innovation) to provide 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
(explicit data-binding supports everything available with implicit data-binding too)
Example (you may copy/paste in girb
):
require 'glimmer-dsl-libui'
include Glimmer
data = [
['Lisa Sky', '[email protected]', '720-523-4329', 'Denver', 'CO'],
['Jordan Biggins', '[email protected]', '617-528-5399', 'Boston', 'MA'],
['Mary Glass', '[email protected]', '847-589-8788', 'Elk Grove Village', 'IL'],
['Darren McGrath', '[email protected]', '206-539-9283', 'Seattle', 'WA'],
['Melody Hanheimer', '[email protected]', '213-493-8274', 'Los Angeles', 'CA'],
]
window('Contacts', 600, 600) {
margined true
vertical_box {
form {
stretchy false
@name_entry = entry {
label 'Name'
}
@email_entry = entry {
label 'Email'
}
@phone_entry = entry {
label 'Phone'
}
@city_entry = entry {
label 'City'
}
@state_entry = entry {
label 'State'
}
}
button('Save Contact') {
stretchy false
on_clicked do
new_row = [@name_entry.text, @email_entry.text, @phone_entry.text, @city_entry.text, @state_entry.text]
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
data << new_row # automatically inserts a row into the table due to implicit data-binding
@unfiltered_data = data.dup
@name_entry.text = ''
@email_entry.text = ''
@phone_entry.text = ''
@city_entry.text = ''
@state_entry.text = ''
end
end
}
search_entry { |se|
stretchy false
on_changed do
filter_value = se.text
@unfiltered_data ||= data.dup
# Unfilter first to remove any previous filters
data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
# Now, apply filter if entered
unless filter_value.empty?
data.filter! do |row_data| # affects table indirectly through implicit data-binding
row_data.any? do |cell|
cell.to_s.downcase.include?(filter_value.downcase)
end
end
end
end
}
table {
text_column('Name')
text_column('Email')
text_column('Phone')
text_column('City')
text_column('State')
editable true
cell_rows data # implicit data-binding to raw data Array of Arrays
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
Mac | Windows | Linux |
---|---|---|
[glimmer-dsl-libui-mac-form-table.png](images/glimmer-dsl-libui-mac-form-table.png) | [glimmer-dsl-libui-windows-form-table.png](images/glimmer-dsl-libui-windows-form-table.png) | [glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png) |
Learn more by checking out examples.
Refined Table
[EARLY ALPHA FEATURE]
refined_table
is a custom control provided exclusively by Glimmer DSL for LibUI
that includes filtering and pagination support out of the box and can handle very large amounts of data (e.g. 50,000 rows).
It is currently an early alpha feature, so please test-drive and report issues if you encounter any. And, please keep in mind that the API might undergo big changes.
Options (passed as kwargs hash):
model_array
(Array
): array of models for which attributes map to table columnsfilter_query
(String
): query term to filter table by; AND-matching multiple words against all columns (e.g.John Illinois
returns John Doe from Illinois, USA), a double-quoted exact term match against all columns (e.g."Urbana Champaign"
returns only results from the town of "Urbana Champaign"), or a match against specific columns (e.g.first_name:John
,"first name":John
,first_name:"John Doe"
, or"first name":"john doe"
). You may mix and match different types of filter queries. All matches are case-insensitive. To customize filtering differently, you may set thefilter
option explained below.table_columns
(Hash
): this maps column types to symbols (e.g.text_column
becomes:text
) with hash options per columntable_editable
(Boolean) [default:false
]: this indicates if all table columns are editable or not.per_page
(Integer
)page
(Integer
)visible_page_count
(Boolean) [default:false
]: shows "of PAGE_COUNT pages" after pageentry
fieldfilter
(Lambda) [default:Glimmer::LibUI::CustomControl::RefinedTable::FILTER_DEFAULT
]: enables setting custom filter that acceptsrow_hash
(mapping table column names to row values) andquery
string as arguments, and it is supposed to returntrue
orfalse
for whether to show a row or filter it out.
API:
refined_model_array
(Array
):model_array
with filtering and pagination applied (useful to grab a table row model by index).filtered_model_array
(Array
):model_array
with filtering applied, but without paginationtable_proxy
: control proxy object for thetable
contained in therefined_table
custom control
If the initial model_array
has no more than a single page of data, then pagination buttons are hidden (but, the filter field remains).
Example code:
refined_table(
model_array: contacts,
table_columns: {
'Name' => {button: {on_clicked: ->(row) {puts row}}},
'Colored Email' => :text_color,
'Phone' => {text: {editable: true}},
'City' => :text,
'State' => :text,
},
table_editable: true, # default value is false
per_page: 20, # row count per page
# page: 1, # initial page is 1
# visible_page_count: true, # page count can be shown if preferred
)
Mac | Windows | Linux |
---|---|---|
[glimmer-dsl-libui-mac-paginated-refined-table.png](images/glimmer-dsl-libui-mac-paginated-refined-table.png) | [glimmer-dsl-libui-windows-paginated-refined-table.png](images/glimmer-dsl-libui-windows-paginated-refined-table.png) | [glimmer-dsl-libui-linux-paginated-refined-table.png](images/glimmer-dsl-libui-linux-paginated-refined-table.png) |
Learn more in the Paginated Refined Table example.
Area API
The area
control is a canvas-like control for drawing paths that can be used in one of two ways:
- Declaratively via stable paths: useful for stable paths that will not change often later on. Simply nest
path
and figures likerectangle
and all drawing logic is generated automatically. Path proxy objects are preserved across redraws assuming there would be relatively few stable paths (mostly for decorative reasons). - Semi-declaratively via on_draw listener dynamic paths: useful for more dynamic paths that will definitely change very often. Open an
on_draw
listener block that receives anarea_draw_params
argument and nestpath
and figures likerectangle
and all drawing logic is generated automatically. Path proxy objects are destroyed (thrown-away) at the end of drawing, thus having less memory overhead for drawing thousands of dynamic paths.
Note that when nesting an area
directly underneath window
(without a layout control like vertical_box
), it is automatically reparented with vertical_box
in between the window
and area
since it would not show up on Linux otherwise.
Here is an example of a declarative area
with a stable path (you may copy/paste in girb
):
require 'glimmer-dsl-libui'
include Glimmer
window('Basic Area', 400, 400) {
margined true
vertical_box {
area {
path { # a stable path is added declaratively
rectangle(0, 0, 400, 400)
fill r: 102, g: 102, b: 204, a: 1.0
}
}
}
}.show
Mac | Windows | Linux |
---|---|---|
[glimmer-dsl-libui-mac-basic-area.png](images/glimmer-dsl-libui-mac-basic-area.png) | [glimmer-dsl-libui-windows-basic-area.png](images/glimmer-dsl-libui-windows-basic-area.png) | [glimmer-dsl-libui-linux-basic-area.png](images/glimmer-dsl-libui-linux-basic-area.png) |
Here is the same example using a semi-declarative area
with on_draw
listener that receives a area_draw_params
argument and a dynamic path (you may copy/paste in girb
):
require 'glimmer-dsl-libui'
include Glimmer
window('Basic Area', 400, 400) {
margined true
vertical_box {
area {
on_draw do |area_draw_params|
path { # a dynamic path is added semi-declaratively inside on_draw block
rectangle(0, 0, 400, 400)
fill r: 102, g: 102, b: 204, a: 1.0
}
end
}
}
}.show
Check examples/dynamic_area.rb for a more detailed semi-declarative example.
Scrolling Area
scrolling_area(width as Numeric = main_window.width, height as Numeric = main_window.height)
is similar to area
, but has the following additional methods:
scroll_to(x as Numeric, y as Numeric, width as Numeric = main_window.width, height as Numeric = main_window.height)
: scrolls tox
/y
location withwidth
andheight
viewport size.set_size(width as Numeric, height as Numeric)
: set size of scrolling area, which must must exceed that of visible viewport in order for scrolling to be enabled.
Mac | Windows | Linux |
---|---|---|
[glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) [glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png) | [glimmer-dsl-libui-windows-dynamic-area.png](images/glimmer-dsl-libui-windows-basic-scrolling-area.png) [glimmer-dsl-libui-windows-dynamic-area-updated.png](images/glimmer-dsl-libui-windows-basic-scrolling-area-scrolled.png) | [glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-basic-scrolling-area.png) [glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-basic-scrolling-area-scrolled.png) |
Check examples/basic_scrolling_area.rb for a more detailed example.
Area Path Shapes
area
can have geometric shapes drawn by adding path
elements.
To add path
shapes under an area
, you can do so:
- Explicitly: by adding
path
underarea
and nesting shapes (e.g.rectangle
) underneath that share the samefill
/stroke
/transform
properties - Implicitly: by adding shapes directly under
area
when the shapes have uniquefill
/stroke
/transform
properties (Glimmer DSL for LibUI automatically constructspath
s as intermediary parents for shapes directly added underarea
)
path
can receive a draw_fill_mode
argument that can accept values :winding
or :alternate
and defaults to :winding
.
Available path
shapes (that can be nested explicitly under path
or implicitly under area
directly):
rectangle(x as Numeric, y as Numeric, width as Numeric, height as Numeric)
square(x as Numeric, y as Numeric, length as Numeric)
arc(x_center as Numeric, y_center as Numeric, radius as Numeric, start_angle as Numeric, sweep as Numeric, is_negative as Boolean)
circle(x_center as Numeric, y_center as Numeric, radius as Numeric)
line(x as Numeric, y as Numeric, end_x = nil as Numeric, end_y = nil as Numeric)
: must be placed in afigure
if onlyx
/y
are specified or haveend_x
/end_y
otherwise if outside offigure
(checkpolyline
/polygon
alternatives that do not require afigure
)bezier(x = nil as Numeric, y = nil as Numeric, c1_x as Numeric, c1_y as Numeric, c2_x as Numeric, c2_y as Numeric, end_x as Numeric, end_y as Numeric)
: must be placed in afigure
ifx
/y
are not specified or havex
/y
as start point otherwise if outside offigure
(checkpolybezier
alternative that does not require afigure
)polygon(point_array as Array of Arrays of Numeric or Array of Numeric)
: shortcut for a closed figure of lines; can receive points as [[x1, y1], [x2, y2], ...] or [x1, y1, x2, y2, ...]polyline(point_array as Array of Arrays of Numeric or Array of Numeric)
: shortcut for an open figure of lines; can receive points as [[x1, y1], [x2, y2], ...] or [x1, y1, x2, y2, ...]polybezier(point_array as Array of Arrays of Numeric or Array of Numeric)
: shortcut for an open figure of beziers; can receive points as [[start_x1, start_y1], [c1_x2, c1_y2, c2_x2, c2_y2, end_x2, end_y2], [c1_x3, c1_y3, c2_x3, c2_y3, end_x3, end_y3], ...] or [start_x1, start_y1, c1_x2, c1_y2, c2_x2, c2_y2, end_x2, end_y2, c1_x3, c1_y3, c2_x3, c2_y3, end_x3, end_y3, ...]figure(x=nil as Numeric, y=nil as Numeric)
(composite that can contain other shapes) (can setclosed true
to connect last point to first point automatically)
Check examples/area_gallery.rb for an overiew of all path
shapes.
Mac | Windows | Linux |
---|---|---|
[glimmer-dsl-libui-mac-area-gallery.png](images/glimmer-dsl-libui-mac-area-gallery.png) | [glimmer-dsl-libui-windows-area-gallery.png](images/glimmer-dsl-libui-windows-area-gallery.png) | [glimmer-dsl-libui-linux-area-gallery.png](images/glimmer-dsl-libui-linux-area-gallery.png) |
Area Path Shape Methods
::parameters
: returns parameters of a shape class#bounding_box
: returnsArray
containing[min_x, min_y, width, height]
#contain?(*point, outline: false, distance_tolerance: 0)
: Returns if point ([x, y]
Array
or args) is inside the shape whenoutline
isfalse
or on the outline whenoutline
istrue
.distance_tolerance
is used whenoutline
istrue
as a fuzz factor for declaring a point on the outline of the shape (e.g. helps users select a shape from its outline more easily).#include?(*point)
: Returns if point ([x, y]
Array
or args) is inside the shape when filled (havingfill
value) or on the outline when stroked (not havingfill
value yetstroke
value only)#perfect_shape
: returns PerfectShape object matching the libui shape.#move_by(x_delta, y_delta)
(alias:translate
): moves (translates) shape by x,y delta#move(x, y)
: moves (translates) shape to x,y coordinates (in the top-left x,y corner of the shape)#min_x
: minimum x coordinate of shape (of top-left corner)#min_y
: minimum y coordinate of shape (of top-left corner)#max_x
: maximum x coordinate of shape (of bottom-right corner)#max_y
: maximum y coordinate of shape (of bottom-right corner)#center_point
(Array
of x,y): center point of shape#center_x
: center x coordinate of shape#center_y
: center y coordinate of shape
Area Text
To draw text
in an area
, you simply nest a text(x, y, width)
control directly under area
or inside a on_draw
listener, and then nest attributed string {[attributes]; string_value}
controls underneath it returning an actual String
(think of them as the <span>
or <p>
element in html, which contains a string of text). Alternatively, you can nest attributed string(string_value) {[attributes]}
if string_value
is a short single-line string. An attributed string
value can be changed dynamically via its string
property.
text
has the following properties:
default_font
:align
::left
(default),:center
, or:right
(align
currently seems not to work on the Mac)x
: x coordinate in relation to parentarea
top-left cornery
: y coordinate in relation to parentarea
top-left cornerwidth
(default: area width - x*2): width of text to display
string
has the following properties:
font
: font descriptor hash consisting of:family
,:size
,:weight
([:minimum, :thin, :ultra_light, :light, :book, :normal, :medium, :semi_bold, :bold, :ultra_bold, :heavy, :ultra_heavy, :maximum]
),:italic
([:normal, :oblique, :italic]
), and:stretch
([:ultra_condensed, :extra_condensed, :condensed, :semi_condensed, :normal, :semi_expanded, :expanded, :extra_expanded, :ultra_expanded]
) key valuescolor
: rgba, hex, or X11 colorbackground
: rgba, hex, or X11 colorunderline
: one of:none
,:single
,:double
,:suggestion
,:color_custom
,:color_spelling
,:color_grammar
,:color_auxiliary
underline_color
: one of:spelling
,:grammar
,:auxiliary
, rgba, hex, or X11 coloropen_type_features
: Open Type Features (https://www.microsoft.com/typography/otspec/featuretags.htm) consist ofopen_type_tag
s nested in content block, which accept (a
,b
,c
,d
,Integer
) arguments.string
: string value (String
)
Example (you may copy/paste in girb
):
require 'glimmer-dsl-libui'
include Glimmer
window('area text drawing') {
area {
text {
default_font family: 'Helvetica', size: 12, weight: :normal, italic: :normal, stretch: :normal
string('This ') {
font size: 20, weight: :bold, italic: :normal, stretch: :normal
color r: 128, g: 0, b: 0, a: 1
}
string('is ') {
font size: 20, weight: :bold, italic: :normal, stretch: :normal
color r: 0, g: 128, b: 0, a: 1
}
string('a ') {
font size: 20, weight: :bold, italic: :normal, stretch: :normal
color r: 0, g: 0, b: 128, a: 1
}
string('short ') {
font size: 20, weight: :bold, italic: :italic, stretch: :normal
color r: 128, g: 128, b: 0, a: 1
}
string('attributed ') {
font size: 20, weight: :bold, italic: :normal, stretch: :normal
color r: 0, g: 128, b: 128, a: 1
}
string("string \n\n") {
font size: 20, weight: :bold, italic: :normal, stretch: :normal
color r: 128, g: 0, b: 128, a: 1
}
string {
font family: 'Georgia', size: 13, weight: :medium, italic: :normal, stretch: :normal
color r: 0, g: 128, b: 255, a: 1
background r: 255, g: 255, b: 0, a: 0.5
underline :single
underline_color :spelling
open_type_features {
open_type_tag 'l', 'i', 'g', 'a', 0
open_type_tag 'l', 'i', 'g', 'a', 1
}
"This is a demonstration\n" \
"of a very long\n" \
"attributed string\n" \
"spanning multiple lines\n\n"
}
}
}
}.show
You may checkout examples/basic_draw_text.rb and examples/custom_draw_text.rb for examples of using text
inside area
.
Mac | Windows | Linux |
---|---|---|
[glimmer-dsl-libui-mac-custom-draw-text-changed.png](images/glimmer-dsl-libui-mac-custom-draw-text-changed.png) | [glimmer-dsl-libui-windows-custom-draw-text-changed.png](images/glimmer-dsl-libui-windows-custom-draw-text-changed.png) | [glimmer-dsl-libui-linux-custom-draw-text-changed.png](images/glimmer-dsl-libui-linux-custom-draw-text-changed.png) |
Area Image
(ALPHA FEATURE)
libui does not support image
rendering outside of table
yet.
However, Glimmer DSL for LibUI adds a special image(file as String path or web URL, width as Numeric, height as Numeric)
custom control that renders an image unto an area
pixel by pixel (and when possible to optimize, line by line).
Given that it is very new and is not a libui-native control, please keep these notes in mind:
- It only supports the
.png
file format. - libui pixel-by-pixel rendering performance is slow.
- Including an
image
inside anarea
on_draw
listener improves performance due to not retaining pixel/line data in memory. - Supplying
width
andheight
options greatly improves performance when shrinking image (e.g.image('somefile.png', width: 24, height: 24)
). You can also supply one of the two dimensions, and the other one gets calculated automatically while preserving original aspect ratio (e.g.image('somefile.png', height: 24)
) - Glimmer DSL for LibUI lets you optionally specify
x
andy
in addition tofile
,width
andheight
(5 arguments total) to offset image location.
Currently, it is recommended to use image
with very small width
and height
values only (e.g. 24x24).
Setting a transform
matrix
is supported under image
just like it is under path
and text
inside area
.
Example of using image
declaratively (you may copy/paste in girb
):
Mac | Windows | Linux |
---|---|---|
[glimmer-dsl-libui-mac-basic-image.png](images/glimmer-dsl-libui-mac-basic-image.png) | [glimmer-dsl-libui-windows-basic-image.png](images/glimmer-dsl-libui-windows-basic-image.png) | [glimmer-dsl-libui-linux-basic-image.png](images/glimmer-dsl-libui-linux-basic-image.png) |
require 'glimmer-dsl-libui'
include Glimmer
window('Basic Image', 96, 96) {
area {
image(File.expand_path('icons/glimmer.png', __dir__), height: 96) # width is automatically calculated from height while preserving original aspect ratio
# image(File.expand_path('icons/glimmer.png', __dir__), width: 96, height: 96) # you can specify both width and height options
# image(File.expand_path('icons/glimmer.png', __dir__), 96, 96) # you can specify width, height as args
# image(File.expand_path('../icons/glimmer.png', __dir__), 0, 0, 96, 96) # you can specify x, y, width, height args as alternative
# image(File.expand_path('../icons/glimmer.png', __dir__), x: 0, y: 0, width: 96, height: 96) # you can specify x, y, width, height options as alternative
}
}.show
Example of better performance via on_draw
(you may copy/paste in girb
):
require 'glimmer-dsl-libui'
include Glimmer
window('Basic Image', 96, 96) {
area {
on_draw do |area_draw_params|
image(File.expand_path('icons/glimmer.png', __dir__), 96, 96)
end
}
}.show
Example of using image
declaratively with explicit properties (you may copy/paste in girb
):
require 'glimmer-dsl-libui'
include Glimmer
window('Basic Image', 96, 96) {
area {
image {
file File.expand_path('icons/glimmer.png', __dir__)
# x 0 # default
# y 0 # default
width 96
height 96
}
}
}.show
Example of better performance via on_draw
with explicit properties (you may copy/paste in girb
):
require 'glimmer-dsl-libui'
include Glimmer
window('Basic Image', 96, 96) {
area {
on_draw do |area_draw_params|
image {
file File.expand_path('icons/glimmer.png', __dir__)
width 96
height 96
}
end
}
}.show
If you need to render an image pixel by pixel (e.g. to support a format other than .png
) for very exceptional scenarios, you may use this example as a guide, including a line-merge optimization for neighboring horizontal pixels with the same color:
# This is the manual way of rendering an image unto an area control.
# It could come in handy in special situations.
# Otherwise, it is recommended to simply utilize the `image` control that
# can be nested under area or area on_draw listener to automate all this work.
require 'glimmer-dsl-libui'
require 'chunky_png'
include Glimmer
puts 'Parsing image...'; $stdout.flush
f = File.open(File.expand_path('icons/glimmer.png', __dir__))
canvas = ChunkyPNG::Canvas.from_io(f)
f.close
canvas.resample_nearest_neighbor!(96, 96)
data = canvas.to_rgba_stream
width = canvas.width
height = canvas.height
puts "Image width: #{width}"
puts "Image height: #{height}"
puts 'Parsing colors...'; $stdout.flush
color_maps = height.times.map do |y|
width.times.map do |x|
r = data[(y*width + x)*4].ord
g = data[(y*width + x)*4 + 1].ord
b = data[(y*width + x)*4 + 2].ord
a = data[(y*width + x)*4 + 3].ord
{x: x, y: y, color: {r: r, g: g, b: b, a: a}}
end
end.flatten
puts "#{color_maps.size} pixels to render..."; $stdout.flush
puts 'Parsing shapes...'; $stdout.flush
shape_maps = []
original_color_maps = color_maps.dup
indexed_original_color_maps = Hash[original_color_maps.each_with_index.to_a]
color_maps.each do |color_map|
index = indexed_original_color_maps[color_map]
@rectangle_start_x ||= color_map[:x]
@rectangle_width ||= 1
if color_map[:x] < width - 1 && color_map[:color] == original_color_maps[index + 1][:color]
@rectangle_width += 1
else
if color_map[:x] > 0 && color_map[:color] == original_color_maps[index - 1][:color]
shape_maps << {x: @rectangle_start_x, y: color_map[:y], width: @rectangle_width, height: 1, color: color_map[:color]}
else
shape_maps << {x: color_map[:x], y: color_map[:y], width: 1, height: 1, color: color_map[:color]}
end
@rectangle_width = 1
@rectangle_start_x = color_map[:x] == width - 1 ? 0 : color_map[:x] + 1
end
end
puts "#{shape_maps.size} shapes to render..."; $stdout.flush
puts 'Rendering image...'; $stdout.flush
window('Basic Image', 96, 96) {
area {
on_draw do |area_draw_params|
shape_maps.each do |shape_map|
path {
rectangle(shape_map[:x], shape_map[:y], shape_map[:width], shape_map[:height])
fill shape_map[:color]
}
end
end
}
}.show
One final note is that in Linux, table images grow and shrink with the image size unlike on the Mac where table row heights are constant regardless of image sizes. As such, you may be able to repurpose a table with a single image column and a single row as an image control with more native libui rendering if you are only targeting Linux with your app.
[linux table image](images/glimmer-dsl-libui-linux-basic-table-image.png)
Check out examples/basic_image.rb (all versions) for examples of using image
Glimmer custom control.
Colors
fill
and stroke
accept X11 color Symbol
s/String
s like :skyblue
and 'sandybrown'
or 6-char hex or 3-char hex-shorthand (as Integer
or String
with or without 0x
prefix)
Available X11 colors can be obtained through Glimmer::LibUI.x11_colors
method.
Check Basic Transform example for use of X11 colors.
Check Histogram example for use of hex colors.
Area Draw Params
The area_draw_params
Hash
argument for on_draw
block is a hash consisting of the following keys:
:context
: the drawing context object:area_width
: area width:area_height
: area height:clip_x
: clip region top-left x coordinate:clip_y
: clip region top-left y coordinate:clip_width
: clip region width:clip_height
: clip region height
In general, it is recommended to use declarative stable paths whenever feasible since they require less code and simpler maintenance. But, in more advanced cases, semi-declarative dynamic paths could be used instead, especially if there are thousands of dynamic paths that need maximum performance and low memory footprint.
Area Listeners
area
supports a number of keyboard and mouse listeners to enable observing the control for user interaction to execute some logic.
The same listeners can be nested directly under area
shapes like rectangle
and circle
, and Glimmer DSL for LibUI will automatically detect when the mouse lands within those shapes to constrain triggering the listeners by the shape regions.
area
supported listeners are:
on_key_event {|area_key_event| ...}
: general catch-all key event (recommend using fine-grained key events below instead)on_key_down {|area_key_event| ...}
on_key_up {|area_key_event| ...}
on_mouse_event {|area_mouse_event| ...}
: general catch-all mouse event (recommend using fine-grained mouse events below instead)on_mouse_down {|area_mouse_event| ...}
on_mouse_up {|area_mouse_event| ...}
on_mouse_drag_started {|area_mouse_event| ...}
on_mouse_dragged {|area_mouse_event| ...}
on_mouse_dropped {|area_mouse_event| ...}
on_mouse_entered {...}
on_mouse_exited {...}
on_mouse_crossed {|left| ...}
(NOT RECOMMENDED; it does whaton_mouse_entered
andon_mouse_exited
do by returning aleft
argument indicating if mouse leftarea
)on_drag_broken {...}
(NOT RECOMMENDED; varies per platforms; useon_mouse_dropped
instead)
The area_mouse_event
Hash
argument for mouse events that receive it (e.g. on_mouse_up
, on_mouse_dragged
) consist of the following hash keys:
:x
: mouse x location in relation to area's top-left-corner:y
: mouse y location in relation to area's top-left-corner:area_width
: area current width:area_height
: area current height:down
: mouse pressed button (e.g.1
is left button,3
is right button):up
: mouse depressed button (e.g.1
is left button,3
is right button):count
: count of mouse clicks (e.g.2
for double-click,1
for single-click):modifers
:Array
ofSymbol
s from one of the following:[:command, :shift, :alt, :control]
:held
: mouse held button during dragging (e.g.1
is left button,4
is right button)
The area_key_event
Hash
argument for keyboard events that receive it (e.g. on_key_up
, on_key_down
) consist of the following hash keys:
:key
: key character (String
):key_value
(alias::key_code
): key code value (Integer
). Useful in rare cases for numeric processing of keys instead of dealing with as:key
characterString
:ext_key
: non-character extra key (Symbol
) fromGlimmer::LibUI.enum_symbols(:ext_key)
such as:left
,:right
,:escape
,:insert
:ext_key_value
: non-character extra key value (Integer
). Useful in rare cases for numeric processing of extra keys instead of dealing with as:ext_key
Symbol
:modifier
: modifier key pressed alone (e.g.:shift
or:control
):modifiers
: modifier keys pressed simultaneously with:key
,:ext_key
, or:modifier
:up
: indicates if key has been released or not (Boolean)
Area Methods/Attributes
To redraw an area
, you may call the #queue_redraw_all
method, or simply #redraw
.
area
has the following Glimmer-added API methods/attributes:
request_auto_redraw
: requests auto redraw upon changes to nested stablepath
or shapespause_auto_redraw
: pause auto redraw upon changes to nested stablepath
or shapes (useful to avoid too many micro-change redraws, to group all redraws as one after many micro-changes)resume_auto_redraw
: resume auto redraw upon changes to nested stablepath
or shapesauto_redraw_enabled
/auto_redraw_enabled?
/auto_redraw_enabled=
: an attribute to disable/enable auto redraw on anarea
upon changes to nested stablepath
or shapes
Area Transform Matrix
A transform matrix
can be set on a path by building a matrix(m11 = nil, m12 = nil, m21 = nil, m22 = nil, m31 = nil, m32 = nil) {operations}
proxy object and then setting via transform
property, or alternatively by building and setting the matrix in one call to transform(m11 = nil, m12 = nil, m21 = nil, m22 = nil, m31 = nil, m32 = nil) {operations}
passing it the matrix arguments and/or content operations.
When instantiating a matrix
object, it always starts with identity matrix.
Here are the following operations that can be performed in a matrix
body:
identity
[alias:set_identity
]: resets matrix to identity matrixtranslate(x as Numeric, y as Numeric)
scale(x_center = 0 as Numeric, y_center = 0 as Numeric, x as Numeric, y as Numeric)
skew(x = 0 as Numeric, y = 0 as Numeric, x_amount as Numeric, y_amount as Numeric)
rotate(x = 0 as Numeric, y = 0 as Numeric, degrees as Numeric)
Example of using transform matrix (you may copy/paste in girb
):
require 'glimmer-dsl-libui'
include Glimmer
window('Basic Transform', 350, 350) {
area {
path {
square(0, 0, 350)
fill r: 255, g: 255, b: 0
}
40.times do |n|
path {
square(0, 0, 100)
fill r: [255 - n*5, 0].max, g: [n*5, 255].min, b: 0, a: 0.5
stroke :black, thickness: 2
transform {
skew 0.15, 0.15
translate 50, 50
rotate 100, 100, -9 * n
scale 1.1, 1.1
}
}
end
}
}.show
Keep in mind that this part could be written differently when there is a need to reuse the matrix:
transform {
translate 100, 100
rotate 100, 100, -9 * n
}
Alternatively:
m1 = matrix {
translate 100, 100
rotate 100, 100, -9 * n
}
transform m1
# and then reuse m1 elsewhere too
You can set a matrix
/transform
on area
directly to conveniently apply to all nested path
s too.
Note that area
, path
, and nested shapes are all truly declarative, meaning they do not care about the ordering of calls to fill
, stroke
, and transform
. Furthermore, any transform that is applied is reversed at the end of the block, so you never have to worry about the ordering of transform
calls among different paths. You simply set a transform on the path
s that need it and it is guaranteed to be called before all its content is drawn, and then undone afterwards to avoid affecting later paths. Matrix transform
can be set on an entire area
too, applying to all nested path
s.
Area Animation
If you need to animate area
vector graphics, you just have to use the Glimmer::LibUI::timer
method along with making changes to shape attributes.
Spinner example that has a fully customizable method-based custom control called spinner
, which is destroyed if you click on it (you may copy/paste in girb
):
require 'glimmer-dsl-libui'
class SpinnerExample
include Glimmer
SIZE = 120
def initialize
create_gui
end
def launch
@main_window.show
end
def create_gui
@main_window = window {
title 'Spinner'
content_size SIZE*2, SIZE*2
horizontal_box {
padded false
vertical_box {
padded false
spinner(size: SIZE)
spinner(size: SIZE, fill_color: [42, 153, 214])
}
vertical_box {
padded false
spinner(size: SIZE/2.0, fill_color: :orange)
spinner(size: SIZE/2.0, fill_color: {x0: 0, y0: 0, x1: SIZE/2.0, y1: SIZE/2.0, stops: [{pos: 0.25, r: 204, g: 102, b: 204}, {pos: 1, r: 2, g: 2, b: 254}]})
spinner(size: SIZE/2.0, fill_color: :green, unfilled_color: :yellow)
spinner(size: SIZE/2.0, fill_color: :white, unfilled_color: :gray, background_color: :black)
}
}
}
end
def spinner(size: 40.0, fill_color: :gray, background_color: :white, unfilled_color: {r: 243, g: 243, b: 243}, donut_percentage: 0.25)
arc1 = arc2 = nil
area { |the_area|
rectangle(0, 0, size, size) {
fill background_color
}
circle(size/2.0, size/2.0, size/2.0) {
fill fill_color
}
arc1 = arc(size/2.0, size/2.0, size/2.0, 0, 180) {
fill unfilled_color
}
arc2 = arc(size/2.0, size/2.0, size/2.0, 90, 180) {
fill unfilled_color
}
circle(size/2.0, size/2.0, (size/2.0)*(1.0 - donut_percentage)) {
fill background_color
}
on_mouse_up do
the_area.destroy
end
}.tap do
Glimmer::LibUI.timer(0.05) do
delta = 10
arc1.start_angle += delta
arc2.start_angle += delta
end
end
end
end
SpinnerExample.new.launch
Smart Defaults and Conventions
horizontal_box
,vertical_box
,grid
, andform
controls havepadded
astrue
upon instantiation to ensure more user-friendly GUI by defaultgroup
controls havemargined
astrue
upon instantiation to ensure more user-friendly GUI by default- All controls nested under a
horizontal_box
,vertical_box
, andform
havestretchy
property (fill maximum space) astrue
by default (passed tobox_append
/form_append
method) - If an event listener is repeated under a control (e.g. two
on_clicked {}
listeners underbutton
), it does not overwrite the previous listener, yet it is added to anArray
of listeners for the event. Glimmer DSL for LibUI provides multiple-event-listener support unlike LibUI window
instatiation args can be left off, having the following defaults when unspecified:title
as''
,width
as190
,height
as150
, andhas_menubar
astrue
)window
has anon_closing
listener by default that quits application upon hitting close button if window is the main window, not a child window (the behavior can be overridden with a manualon_closing
implementation that returns integer1
/true
for closing and0
/false
for remaining open).group
hastitle
property default to''
if not specified in instantiation args, so it can be instantiated without args withtitle
property specified in nested block (e.g.group {title 'Address'; ...}
)button
,checkbox
, andlabel
havetext
default to''
if not specified in instantiation args, so they can be instantiated without args withtext
property specified in nested block (e.g.button {text 'Greet'; on_clicked {puts 'Hello'}}
)quit_menu_item
has anon_clicked
listener by default that quits application upon selecting the quit menu item (can be overridden with a manualon_clicked
implementation that returns integer0
for success)- If an
on_closing
listener was defined onwindow
and it does not return an integer, default exit behavior is assumed (window.destroy
is called followed byLibUI.quit
, returning0
). - If multiple
on_closing
listeners were added forwindow
, and none return an integer, they are all executed. On the other hand, if one of them returns an integer, it is counted as the final return value and stops the chain of listener execution. - If an
on_clicked
listener was defined onquit_menu_item
and it does not return an integer, default exit behavior is assumed (quit_menu_item.destroy
andmain_window.destroy
are called followed byLibUI.quit
, returning0
). - If multiple
on_clicked
listeners were added forquit_menu_item
, and none return an integer, they are all executed. On the other hand, if one of them returns an integer, it is counted as the final return value and stops the chain of listener execution. - All boolean property readers return
true
orfalse
in Ruby instead of the libui original0
or1
in C. - All boolean property writers accept
true
/false
in addition to1
/0
in Ruby - All string property readers return a
String
object in Ruby instead of the libui Fiddle pointer object. - Automatically allocate font descriptors upon instantiating
font_button
controls and free them when destroyingfont_button
controls - Automatically allocate color value pointers upon instantiating
color_button
controls and free them when destroyingcolor_button
controls - On the Mac, if no
menu
items were added, an automaticquit_menu_item
is added to enable quitting with CTRL+Q - When destroying a control nested under a
horizontal_box
orvertical_box
, it is automatically deleted from the box's children - When destroying a control nested under a
form
, it is automatically deleted from the form's children - When destroying a control nested under a
window
orgroup
, it is automatically unset as their child to allow successful destruction - When destroying a control that has a data-binding to a model attribute, the data-binding observer registration is automatically deregistered
- For
date_time_picker
,date_picker
, andtime_picker
, make suretime
hash values formon
,wday
, andyday
are 1-based instead of libui original 0-based values, and returndst
as Boolean instead ofisdst
as1
/0
- Smart defaults for
grid
child properties areleft
(0
),top
(0
),xspan
(1
),yspan
(1
),hexpand
(false
),halign
(:fill
),vexpand
(false
), andvalign
(:fill
) - The
table
control automatically constructs requiredTableModelHandler
,TableModel
, andTableParams
, calculating all their arguments fromcell_rows
andeditable
properties (e.g.NumRows
) as well as nested columns (e.g.text_column
) - Table model instances are automatically freed from memory after
window
is destroyed. - Table
cell_rows
data has implicit data-binding to table cell values for deletion, insertion, and change (done by diffingcell_rows
value before and after change and auto-informingtable
of deletions [LibUI.table_model_row_deleted
], insertions [LibUI.table_model_row_deleted
], and changes [LibUI.table_model_row_changed
]). When deleting data rows fromcell_rows
array, then actual rows from thetable
are automatically deleted. When inserting data rows intocell_rows
array, then actualtable
rows are automatically inserted. When updating data rows incell_rows
array, then actualtable
rows are automatically updated. image
instances are automatically freed from memory afterwindow
is destroyed.image
width
andheight
can be left off if it has oneimage_part
only as they default to the samewidth
andheight
of theimage_part
- Automatically provide shifted
:key
characters inarea_key_event
provided inarea
key listenerson_key_event
,on_key_down
, andon_key_up
scrolling_area
width
andheight
default to main window width and height if not specified.scrolling_area
#scroll_to
3rd and 4th arguments (width
andheight
) default to main window width and height if not specified.area
paths are specified declaratively with shapes/figures underneath (e.g.rectangle
), andarea
draw listener is automatically generatedarea
path shapes can be added directly underarea
without declaringpath
explicitly as a convenient shorthandline
andbezier
automatically start a new figure if placed outside offigure
- Observe figure properties (e.g.
rectangle
width
) for changes and automatically redraw containing area accordingly - Observe
path
fill
andstroke
hashes for changes and automatically redraw containing area accordingly - Observe
text
andstring
properties for changes and automatically redraw containing area accordingly - All controls are protected from garbage collection until no longer needed (explicitly destroyed), so there is no need to worry about surprises.
- All resources are freed automatically once no longer needed or left to garbage collection.
- When nesting an
area
directly underneathwindow
(without a layout control likevertical_box
), it is automatically reparented withvertical_box
in between thewindow
andarea
since it would not show up on Linux otherwise. - Colors may be passed in as a hash of
:r
,:g
,:b
,:a
, or:red
,:green
,:blue
,:alpha
, or X11 color like:skyblue
, or 6-char hex or 3-char hex (asInteger
orString
with or without0x
prefix) - Color alpha value defaults to
1.0
when not specified.
Custom Keywords
Custom keywords can be defined to represent custom controls (components) that provide new features or act as composites of existing controls that need to be reused multiple times in an application or across multiple applications. Custom keywords save a lot of development time, improving productivity and maintainability immensely.
For example, you can define a custom address_view
control as an aggregate of multiple label
controls to reuse multiple times as a standard address View, displaying street, city, state, and zip code.
There are two ways to define custom keywords:
- Method-Based: simply define a method representing the custom control you want (e.g.
address_view
) with any arguments needed (e.g.address(address_model)
). - Class-Based: define a class matching the camelcased name of the custom control by convention (e.g. the
address_view
custom control keyword would have a class calledAddressView
) andinclude Glimmer::LibUI::CustomControl
. Classes add the benefit of being able to distribute the custom controls into separate files and reuse externally from multiple places or share via Ruby gems.
It is OK to use the terms "custom keyword" and "custom control" synonymously though "custom keyword" is a broader term that covers things other than controls too like custom shapes (e.g. cylinder
), custom attributed strings (e.g. alternating_color_string
), and custom transforms (isometric_transform
).
Example that defines form_field
, address_form
, label_pair
, and address_view
keywords (you may copy/paste in girb
):
require 'glimmer-dsl-libui'
require 'facets'
include Glimmer
Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
def form_field(model, attribute)
attribute = attribute.to_s
entry { |e|
label attribute.underscore.split('_').map(&:capitalize).join(' ')
text <=> [model, attribute]
}
end
def address_form(address_model)
form {
form_field(address_model, :street)
form_field(address_model, :p_o_box)
form_field(address_model, :city)
form_field(address_model, :state)
form_field(address_model, :zip_code)
}
end
def label_pair(model, attribute, value)
horizontal_box {
label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
label(value.to_s) {
text <= [model, attribute]
}
}
end
def address_view(address_model)
vertical_box {
address_model.each_pair do |attribute, value|
label_pair(address_model, attribute, value)
end
}
end
address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
window('Method-Based Custom Keyword') {
margined true
horizontal_box {
vertical_box {
label('Address 1') {
stretchy false
}
address_form(address1)
horizontal_separator {
stretchy false
}
label('Address 1 (Saved)') {
stretchy false
}
address_view(address1)
}
vertical_separator {
stretchy false
}
vertical_box {
label('Address 2') {
stretchy false
}
address_form(address2)
horizontal_separator {
stretchy false
}
label('Address 2 (Saved)') {
stretchy false
}
address_view(address2)
}
}
}.show
[glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png)
You can also define Custom Window keywords, that is custom controls with window
being the body root. These are also known as Applications. To define a Custom Window, you include Glimmer::LibUI::CustomWindow
or include Glimmer:LibUI::Application
and then you can invoke the ::launch
method on the class.
Example (you may copy/paste in girb
):
require 'glimmer-dsl-libui'
require 'facets'
Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
class FormField
include Glimmer::LibUI::CustomControl
options :model, :attribute
body {
entry { |e|
label attribute.to_s.underscore.split('_').map(&:capitalize).join(' ')
text <=> [model, attribute]
}
}
end
class AddressForm
include Glimmer::LibUI::CustomControl
options :address
body {
form {
form_field(model: address, attribute: :street)
form_field(model: address, attribute: :p_o_box)
form_field(model: address, attribute: :city)
form_field(model: address, attribute: :state)
form_field(model: address, attribute: :zip_code)
}
}
end
class LabelPair
include Glimmer::LibUI::CustomControl
options :model, :attribute, :value
body {
horizontal_box {
label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
label(value.to_s) {
text <= [model, attribute]
}
}
}
end
class AddressView
include Glimmer::LibUI::CustomControl
options :address
body {
vertical_box {
address.each_pair do |attribute, value|
label_pair(model: address, attribute: attribute, value: value)
end
}
}
end
class ClassBasedCustomControls
include Glimmer::LibUI::Application # alias: Glimmer::LibUI::CustomWindow
before_body do
@address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
@address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
end
body {
window('Class-Based Custom Keyword') {
margined true
horizontal_box {
vertical_box {
label('Address 1') {
stretchy false
}
address_form(address: @address1)
horizontal_separator {
stretchy false
}
label('Address 1 (Saved)') {
stretchy false
}
address_view(address: @address1)
}
vertical_separator {
stretchy false
}
vertical_box {
label('Address 2') {
stretchy false
}
address_form(address: @address2)
horizontal_separator {
stretchy false
}
label('Address 2 (Saved)') {
stretchy false
}
address_view(address: @address2)
}
}
}
}
end
ClassBasedCustomControls.launch
[glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png)
The area
control can be utilized to build non-native custom controls from scratch by leveraging vector graphics, formattable text, keyboard events, and mouse events. This is demonstrated in the Area-Based Custom Controls example.
Defining custom keywords enables unlimited extension of the Glimmer GUI DSL. The sky is the limit on what can be done with custom keywords as a result. You can compose new visual vocabulary to build applications in any domain from higher concepts rather than mere standard controls. For example, in a traffic signaling app, you could define street
, light_signal
, traffic_sign
, and car
as custom keywords and build your application from these concepts directly, saving enormous time and achieving much higher productivity.
Learn more from custom keyword usage in Method-Based Custom Keyword, Area-Based Custom Controls, Basic Scrolling Area, Histogram, and Tetris examples.
Observer Pattern
The Observer Design Pattern (a.k.a. Observer Pattern) is fundamental to building GUIs (Graphical User Interfaces) following the MVC (Model View Controller) Architectural Pattern or any of its variations like MVP (Model View Presenter). In the original Smalltalk-MVC, the View observes the Model for changes and updates itself accordingly.
Glimmer DSL for LibUI supports the Observer Design Pattern via the observe(model, attribute_or_key=nil)
keyword, which can observe Object
models with attributes, Hash
es with keys, and Array
s. It automatically enhances objects as needed to support automatically notifying observers of changes via observable#notify_observers(attribute_or_key = nil)
method:
Object
becomesGlimmer::DataBinding::ObservableModel
, which supports observing specifiedObject
model attributes.Hash
becomesGlimmer::DataBinding::ObservableHash
, which supports observing allHash
keys or a specificHash
keyArray
becomesGlimmer::DataBinding::ObservableArray
, which supports observingArray
changes like those done withpush
,<<
,delete
, andmap!
methods (all mutation methods).
Example:
observe(person, :name) do |new_name|
@name_label.text = new_name
end
That observes a person's name attribute for changes and updates the name label
text
property accordingly.
See examples of the observe
keyword at Color The Circles, Method-Based Custom Keyword, Snake, and Tetris.
Data-Binding
Glimmer DSL for LibUI supports both bidirectional (two-way) data-binding and unidirectional (one-way) data-binding.
Data-binding enables writing very expressive, terse, and declarative code to synchronize View properties with Model attributes without writing many lines or pages of imperative code doing the same thing, increasing productivity immensely.
Data-binding automatically takes advantage of the Observer Pattern behind the scenes and is very well suited to declaring View property data sources piecemeal. On the other hand, explicit use of the Observer Pattern is sometimes more suitable when needing to make multiple View updates upon a single Model attribute change.
Data-binding supports utilizing the MVP (Model View Presenter) flavor of MVC by observing both the View and a Presenter for changes and updating the opposite side upon encountering them. This enables writing more decoupled cleaner code that keeps View code and Model code disentangled and highly maintainable. For example, check out the Snake game presenters for Grid and Cell, which act as proxies for the actual Snake game models Snake and Apple, mediating synchronization of data between them and the Snake View GUI.
Bidirectional (Two-Way) Data-Binding
Glimmer DSL for LibUI supports bidirectional (two-way) data-binding of the following controls/properties via the <=>
operator (indicating data is moving in both directions between View and Model):
checkbox
:checked
check_menu_item
:checked
color_button
:color
combobox
:selected
,selected_item
date_picker
:time
date_time_picker
:time
editable_combobox
:text
entry
:text
font_button
:font
multiline_entry
:text
non_wrapping_multiline_entry
:text
radio_buttons
:selected
radio_menu_item
:checked
search_entry
:text
slider
:value
spinbox
:value
table
:cell_rows
(explicit data-binding by using<=>
and implicit data-binding by assigning value directly)time_picker
:time
Example of bidirectional data-binding:
entry {
text <=> [contract, :legal_text]
}
That is data-binding a contract's legal text to an entry
text
property.
Another example of bidirectional data-binding with an option:
entry {
text <=> [self, :entered_text, after_write: ->(text) {puts text}]
}
That is data-binding entered_text
attribute on self
to entry
text
property and printing text after write to the model.
Table Data-Binding
One note about table
cell_rows
data-binding is that it works with either:
- Raw data
Array
(rows) ofArray
s (column cells) - Model
Array
(rows) of objects having attributes (column cells) matching the underscored names oftable
columns by convention. Model attribute names can be overridden when needed by passing anArray
enumerating all mapped model attributes in the order oftable
columns or alternatively aHash
mapping only the column names that have model attribute names different from their table column underscored version.
Example of table
implicit data-binding of cell_rows
to raw data Array
of Array
s (you may copy/paste in girb
):
require 'glimmer-dsl-libui'
include Glimmer
data = [
['Lisa Sky', '[email protected]', '720-523-4329', 'Denver', 'CO'],
['Jordan Biggins', '[email protected]', '617-528-5399', 'Boston', 'MA'],
['Mary Glass', '[email protected]', '847-589-8788', 'Elk Grove Village', 'IL'],
['Darren McGrath', '[email protected]', '206-539-9283', 'Seattle', 'WA'],
['Melody Hanheimer', '[email protected]', '213-493-8274', 'Los Angeles', 'CA'],
]
window('Contacts', 600, 600) {
table {
text_column('Name')
text_column('Email')
text_column('Phone')
text_column('City')
text_column('State')
cell_rows data
}
}.show
Example of table
explicit data-binding of cell_rows
to Model Array
(you may copy/paste in girb
):
require 'glimmer-dsl-libui'
class SomeTable
Contact = Struct.new(:name, :email, :phone, :city, :state)
include Glimmer
attr_accessor :contacts
def initialize
@contacts = [
Contact.new('Lisa Sky', '[email protected]', '720-523-4329', 'Denver', 'CO'),
Contact.new('Jordan Biggins', '[email protected]', '617-528-5399', 'Boston', 'MA'),
Contact.new('Mary Glass', '[email protected]', '847-589-8788', 'Elk Grove Village', 'IL'),
Contact.new('Darren McGrath', '[email protected]', '206-539-9283', 'Seattle', 'WA'),
Contact.new('Melody Hanheimer', '[email protected]', '213-493-8274', 'Los Angeles', 'CA'),
]
end
def launch
window('Contacts', 600, 200) {
table {
text_column('Name')
text_column('Email')
text_column('Phone')
text_column('City')
text_column('State')
cell_rows <=> [self, :contacts] # explicit data-binding to self.contacts Model Array, auto-inferring model attribute names from underscored table column names by convention
}
}.show
end
end
SomeTable.new.launch
Example of table
explicit data-binding of cell_rows
to Model Array
with column_attributes
Hash
mapping for custom column names (you may copy/paste in girb
):
require 'glimmer-dsl-libui'
class SomeTable
Contact = Struct.new(:name, :email, :phone, :city, :state)
include Glimmer
attr_accessor :contacts
def initialize
@contacts = [
Contact.new('Lisa Sky', '[email protected]', '720-523-4329', 'Denver', 'CO'),
Contact.new('Jordan Biggins', '[email protected]', '617-528-5399', 'Boston', 'MA'),
Contact.new('Mary Glass', '[email protected]', '847-589-8788', 'Elk Grove Village', 'IL'),
Contact.new('Darren McGrath', '[email protected]', '206-539-9283', 'Seattle', 'WA'),
Contact.new('Melody Hanheimer', '[email protected]', '213-493-8274', 'Los Angeles', 'CA'),
]
end
def launch
window('Contacts', 600, 200) {
table {
text_column('Name')
text_column('Email')
text_column('Phone')
text_column('City/Town')
text_column('State/Province')
cell_rows <=> [self, :contacts, column_attributes: {'City/Town' => :city, 'State/Province' => :state}]
}
}.show
end
end
SomeTable.new.launch
Example of table
explicit data-binding of cell_rows
to Model Array
with complete column_attributes
Array
mapping (you may copy/paste in girb
):
require 'glimmer-dsl-libui'
class SomeTable
Contact = Struct.new(:name, :email, :phone, :city, :state)
include Glimmer
attr_accessor :contacts
def initialize
@contacts = [
Contact.new('Lisa Sky', '[email protected]', '720-523-4329', 'Denver', 'CO'),
Contact.new('Jordan Biggins', '[email protected]', '617-528-5399', 'Boston', 'MA'),
Contact.new('Mary Glass', '[email protected]', '847-589-8788', 'Elk Grove Village', 'IL'),
Contact.new('Darren McGrath', '[email protected]', '206-539-9283', 'Seattle', 'WA'),
Contact.new('Melody Hanheimer', '[email protected]', '213-493-8274', 'Los Angeles', 'CA'),
]
end
def launch
window('Contacts', 600, 200) {
table {
text_column('Full Name')
text_column('Email Address')
text_column('Phone Number')
text_column('City or Town')
text_column('State or Province')
cell_rows <=> [self, :contacts, column_attributes: [:name, :email, :phone, :city, :state]]
}
}.show
end
end
SomeTable.new.launch
Unidirectional (One-Way) Data-Binding
Glimmer DSL for LibUI supports unidirectional (one-way) data-binding of any control/shape/attributed-string property via the <=
operator (indicating data is moving from the right side, which is the Model, to the left side, which is the GUI View object).
Example of unidirectional data-binding:
square(0, 0, CELL_SIZE) {
fill <= [@grid.cells[row][column], :color]
}
That is data-binding a grid cell color to a square
shape's fill
property. That means if the color
attribute of the grid cell is updated, the fill
property of the square
shape is automatically updated accordingly.
Another Example of unidirectional data-binding with an option:
window {
title <= [@game, :score, on_read: -> (score) {"Glimmer Snake (Score: #{@game.score})"}]
}
That is data-binding the window
title
property to the score
attribute of a @game
, but converting on read from the Model to a String
.
Data-Binding API
To summarize the data-binding API:
view_property <=> [model, attribute, *read_or_write_options]
: Bidirectional (two-way) data-binding to Model attribute accessorview_property <= [model, attribute, *read_only_options]
: Unidirectional (one-way) data-binding to Model attribute reader
This is also known as the Glimmer Shine syntax for data-binding, a Glimmer-only unique innovation that takes advantage of Ruby's highly expressive syntax and malleable DSL support.
Data-bound model attribute can be:
- Direct:
Symbol
representing attribute reader/writer (e.g.[person, :name
]) - Nested:
String
representing nested attribute path (e.g.[company, 'address.street']
). That results in "nested data-binding" - Indexed:
String
containing array attribute index (e.g.[customer, 'addresses[0].street']
). That results in "indexed data-binding" - Keyed:
String
containing hash attribute key (e.g.[customer, 'addresses[:main].street']
). That results in "keyed data-binding"
Data-binding options include:
before_read {|value| ...}
: performs an operation before reading data from Model to update View.on_read {|value| ...}
: converts value read from Model to update the View.after_read {|converted_value| ...}
: performs an operation after read from Model to update View.before_write {|value| ...}
: performs an operation before writing data to Model from View.on_write {|value| ...}
: converts value read from View to update the Model.after_write {|converted_value| ...}
: performs an operation after writing to Model from View.computed_by attribute
orcomputed_by [attribute1, attribute2, ...]
: indicates model attribute is computed from specified attribute(s), thus updated when they are updated (see in Login example version 2). That is known as "computed data-binding".
Note that with both on_read
and on_write
converters, you could pass a Symbol
representing the name of a method on the value object to invoke.
Example:
entry {
text <=> [product, :price, on_read: :to_s, on_write: :to_i]
}
Learn more from data-binding usage in Login (4 data-binding versions), Basic Entry, Form, Form Table (5 data-binding versions), Method-Based Custom Keyword, Snake and Tic Tac Toe examples.
Data-Binding Gotchas
- Never data-bind a control property to an attribute on the same view object with the same exact name (e.g. binding
entry
text
property toself
text
attribute) as it would conflict with it. Instead, data-bind view property to an attribute with a different name on the view object or with the same name, but on a presenter or model object (e.g. data-bindentry
text
toself
legal_text
attribute or tocontract
modeltext
attribute) - Data-binding a property utilizes the control's listener associated with the property (e.g.
on_changed
forentry
text
), so although you can add another listener if you want (Glimmer DSL for LibUI will happily add your listener to the list of listeners that will get notified by a certain event), sometimes it is recommended that you add anafter_read: ->(val) {}
orafter_write: ->(val) {}
block instead to perform something after data-binding reads from or writes to the Model attribute. - Data-binding a View control to another View control directly is not a good practice as it causes tight-coupling. Instead, data-bind both View controls to the same Presenter/Model attribute, and that keeps them in sync while keeping the code decoupled.
API Gotchas
- There is no proper way to destroy
grid
children due to libui not offering any API for deleting them fromgrid
(nogrid_delete
similar tobox_delete
forhorizontal_box
andvertical_box
). table
checkbox_column
checkbox editing only works on Linux and Windows (not Mac) due to a current limitation in libui.table
checkbox_text_column
checkbox editing only works on Linux (not Mac or Windows) due to a current limitation in libui.text
align
property seems not to work on the Mac (libui has an issue about it)text
string
background
does not work on Windows due to an issue in libui.table
progress_bar
column on Windows cannot be updated with a positive value if it started initially with-1
(it ignores update to avoid crashing due to an issue in libui on Windows.radio_buttons
on Linux has an issue where it always selects the first item even if you did not set itsselected
value or set it to-1
(meaning unselected). It works correctly on Mac and Windows.- It seems that libui does not support nesting multiple
area
controls under agrid
as only the first one shows up in that scenario. To workaround that limitation, use avertical_box
with nestedhorizontal_box
s instead to include multiplearea
s in a GUI. - As per the code of examples/basic_transform.rb, Windows requires different ordering of transforms than Mac and Linux.
scrolling_area#scroll_to
does not seem to work on Windows and Linux, but works fine on Mac
Original API
Here are all the lower-level LibUI API methods utilized by Glimmer DSL for LibUI:
alloc_control
, append_features
, area_begin_user_window_move
, area_begin_user_window_resize
, area_queue_redraw_all
, area_scroll_to
, area_set_size
, attribute_color
, attribute_family
, attribute_features
, attribute_get_type
, attribute_italic
, attribute_size
, attribute_stretch
, attribute_underline
, attribute_underline_color
, attribute_weight
, attributed_string_append_unattributed
, attributed_string_byte_index_to_grapheme
, attributed_string_delete
, attributed_string_for_each_attribute
, attributed_string_grapheme_to_byte_index
, attributed_string_insert_at_unattributed
, attributed_string_len
, attributed_string_num_graphemes
, attributed_string_set_attribute
, attributed_string_string
, box_append
, box_delete
, box_padded
, box_set_padded
, button_on_clicked
, button_set_text
, button_text
, checkbox_checked
, checkbox_on_toggled
, checkbox_set_checked
, checkbox_set_text
, checkbox_text
, color_button_color
, color_button_on_changed
, color_button_set_color
, combobox_append
, combobox_on_selected
, combobox_selected
, combobox_set_selected
, control_destroy
, control_disable
, control_enable
, control_enabled
, control_enabled_to_user
, control_handle
, control_hide
, control_parent
, control_set_parent
, control_show
, control_toplevel
, control_verify_set_parent
, control_visible
, date_time_picker_on_changed
, date_time_picker_set_time
, date_time_picker_time
, draw_clip
, draw_fill
, draw_free_path
, draw_free_text_layout
, draw_matrix_invert
, draw_matrix_invertible
, draw_matrix_multiply
, draw_matrix_rotate
, draw_matrix_scale
, draw_matrix_set_identity
, draw_matrix_skew
, draw_matrix_transform_point
, draw_matrix_transform_size
, draw_matrix_translate
, draw_new_path
, draw_new_text_layout
, draw_path_add_rectangle
, draw_path_arc_to
, draw_path_bezier_to
, draw_path_close_figure
, draw_path_end
, draw_path_line_to
, draw_path_new_figure
, draw_path_new_figure_with_arc
, draw_restore
, draw_save
, draw_stroke
, draw_text
, draw_text_layout_extents
, draw_transform
, editable_combobox_append
, editable_combobox_on_changed
, editable_combobox_set_text
, editable_combobox_text
, entry_on_changed
, entry_read_only
, entry_set_read_only
, entry_set_text
, entry_text
, ffi_lib
, ffi_lib=
, font_button_font
, font_button_on_changed
, form_append
, form_delete
, form_padded
, form_set_padded
, free_attribute
, free_attributed_string
, free_control
, free_font_button_font
, free_image
, free_init_error
, free_open_type_features
, free_table_model
, free_table_value
, free_text
, grid_append
, grid_insert_at
, grid_padded
, grid_set_padded
, group_margined
, group_set_child
, group_set_margined
, group_set_title
, group_title
, image_append
, init
, label_set_text
, label_text
, main
, main_step
, main_steps
, menu_append_about_item
, menu_append_check_item
, menu_append_item
, menu_append_preferences_item
, menu_append_quit_item
, menu_append_separator
, menu_item_checked
, menu_item_disable
, menu_item_enable
, menu_item_on_clicked
, menu_item_set_checked
, msg_box
, msg_box_error
, multiline_entry_append
, multiline_entry_on_changed
, multiline_entry_read_only
, multiline_entry_set_read_only
, multiline_entry_set_text
, multiline_entry_text
, new_area
, new_attributed_string
, new_background_attribute
, new_button
, new_checkbox
, new_color_attribute
, new_color_button
, new_combobox
, new_date_picker
, new_date_time_picker
, new_editable_combobox
, new_entry
, new_family_attribute
, new_features_attribute
, new_font_button
, new_form
, new_grid
, new_group
, new_horizontal_box
, new_horizontal_separator
, new_image
, new_italic_attribute
, new_label
, new_menu
, new_multiline_entry
, new_non_wrapping_multiline_entry
, new_open_type_features
, new_password_entry
, new_progress_bar
, new_radio_buttons
, new_scrolling_area
, new_search_entry
, new_size_attribute
, new_slider
, new_spinbox
, new_stretch_attribute
, new_tab
, new_table
, new_table_model
, new_table_value_color
, new_table_value_image
, new_table_value_int
, new_table_value_string
, new_time_picker
, new_underline_attribute
, new_underline_color_attribute
, new_vertical_box
, new_vertical_separator
, new_weight_attribute
, new_window
, on_should_quit
, open_file
, open_type_features_add
, open_type_features_clone
, open_type_features_for_each
, open_type_features_get
, open_type_features_remove
, progress_bar_set_value
, progress_bar_value
, queue_main
, quit
, radio_buttons_append
, radio_buttons_on_selected
, radio_buttons_selected
, radio_buttons_set_selected
, save_file
, slider_on_changed
, slider_set_value
, slider_value
, spinbox_on_changed
, spinbox_set_value
, spinbox_value
, tab_append
, tab_delete
, tab_insert_at
, tab_margined
, tab_num_pages
, tab_set_margined
, table_append_button_column
, table_append_checkbox_column
, table_append_checkbox_text_column
, table_append_image_column
, table_append_image_text_column
, table_append_progress_bar_column
, table_append_text_column
, table_model_row_changed
, table_model_row_deleted
, table_model_row_inserted
, table_value_color
, table_value_get_type
, table_value_image
, table_value_int
, table_value_string
, timer
, uninit
, user_bug_cannot_set_parent_on_toplevel
, window_borderless
, window_content_size
, window_fullscreen
, window_margined
, window_on_closing
, window_on_content_size_changed
, window_set_borderless
, window_set_child
, window_set_content_size
, window_set_fullscreen
, window_set_margined
, window_set_title
, window_title
To learn more about the LibUI API exposed through Glimmer DSL for LibUI:
- Check out LibUI ffi.rb
- Check out the libui C Headers
- Check out the Go UI (Golang LibUI) API Documentation for an alternative well-documented libui reference.
Packaging
I am documenting options for packaging, which I have not tried myself, but figured they would still be useful to add to the README.md until I can expand further effort into supporting packaging.
For Windows, the LibUI project recommends OCRA (One-Click Ruby Application), which builds Windows executables from Ruby source.
For Mac, consider Platypus (builds a native Mac app from a Ruby script)
For Linux, simply package your app as a Ruby Gem and build rpm package from Ruby Gem or build deb package from Ruby Gem.
Also, there is a promising project called ruby-packer that supports all platforms.
Glimmer Style Guide
1 - Control arguments are always wrapped by parentheses.
Example:
label('Name')
2 - Control blocks are always declared with curly braces to clearly visualize hierarchical view code and separate from logic code.
Example:
group('Basic Controls') {
vertical_box {
button('Button') {
}
}
}
3 - Control property declarations always have arguments that are not wrapped inside parentheses and typically do not take a block.
Example:
stretchy false
value 42
4 - Control listeners are always declared starting with on_ prefix and affixing listener event method name afterwards in underscored lowercase form. Their multi-line blocks have a do; end
style.
Example:
button('Click') {
on_clicked do
msg_box('Information', 'You clicked the button')
end
}
5 - Iterator multi-line blocks always have do; end
style to clearly separate logic code from view code.
Example:
@field_hash.keys.each do |field|
label(field) {
stretchy false
}
entry {
on_changed do |control|
@field_hash[field] = control.text
end
}
end
6 - In a widget's content block, attributes are declared first, with layout management attributes on top (e.g. stretchy false
); an empty line separates attributes from nested widgets and listeners following afterwards.
Example:
group('Numbers') {
stretchy false
vertical_box {
spinbox(0, 100) {
stretchy false
value 42
on_changed do |s|
puts "New Spinbox value: #{s.value}"
$stdout.flush # for Windows
end
}
}
}
7 - Unlike attributes, nested widgets with a content block and listeners are always separated from each other by an empty line to make readability easier except where it helps to group two widgets together (e.g. label and described entry).
Example:
area {
path { # needs an empty line afterwards
square(0, 0, 100) # does not have a content block, so no empty line is needed
square(100, 100, 400) # does not have a content block, so no empty line is needed
fill r: 102, g: 102, b: 204
}
path { # needs an empty line afterwards
rectangle(0, 100, 100, 400) # does not have a content block, so no empty line is needed
rectangle(100, 0, 400, 100) # does not have a content block, so no empty line is needed
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) { # needs an empty line afterwards
fill r: 202, g: 102, b: 104, a: 0.5 # attributes do not need an empty line separator
stroke r: 0, g: 0, b: 0 # attributes do not need an empty line separator
}
on_mouse_up do |area_mouse_event| # needs an empty line afterwards
puts 'mouse up'
end
on_key_up do |area_key_event| # needs an empty line afterwards
puts 'key up'
end
}
Examples
The following basic and advanced examples include reimplementions of the examples in the LibUI project utilizing the Glimmer GUI DSL (with and without data-binding) as well as brand new examples.
To browse all examples, simply launch the [Meta-Example](examples/meta_example.rb), which lists all examples and displays each example's code when selected. It also enables code editing to facilitate experimentation and learning.
[examples/meta_example.rb](examples/meta_example.rb)
Run with this command from the root of the project if you cloned the project:
ruby -r './lib/glimmer-dsl-libui' examples/meta_example.rb
Run with this command if you installed the Ruby gem:
ruby -r glimmer-dsl-libui -e "require 'examples/meta_example'"
Mac | Windows | Linux |
---|---|---|
[glimmer-dsl-libui-mac-meta-example.png](images/glimmer-dsl-libui-mac-meta-example.png) | [glimmer-dsl-libui-windows-meta-example.png](images/glimmer-dsl-libui-windows-meta-example.png) | [glimmer-dsl-libui-linux-meta-example.png](images/glimmer-dsl-libui-linux-meta-example.png) |
New Glimmer DSL for LibUI Version:
require 'glimmer-dsl-libui'
require 'facets'
require 'fileutils'
class MetaExample
include Glimmer
ADDITIONAL_BASIC_EXAMPLES = ['Color Button', 'Font Button', 'Form', 'Date Time Picker', 'Simple Notepad']
attr_accessor :code_text
def initialize
@selected_example_index = examples_with_versions.index(basic_examples_with_versions.first)
@code_text = File.read(file_path_for(selected_example))
end
def examples
if @examples.nil?
example_files = Dir.glob(File.join(File.expand_path('.', __dir__), '*.rb'))
example_file_names = example_files.map { |f| File.basename(f, '.rb') }
example_file_names = example_file_names.reject { |f| f == 'meta_example' || f.match(/\d$/) }
@examples = example_file_names.map { |f| f.underscore.titlecase }
end
@examples
end
def examples_with_versions
examples.map do |example|
version_count_for(example) > 1 ? "#{example} (#{version_count_for(example)} versions)" : example
end
end
def basic_examples_with_versions
examples_with_versions.select {|example| example.start_with?('Basic') || ADDITIONAL_BASIC_EXAMPLES.include?(example) }
end
def advanced_examples_with_versions
examples_with_versions - basic_examples_with_versions
end
def file_path_for(example)
File.join(File.expand_path('.', __dir__), "#{example.underscore}.rb")
end
def version_count_for(example)
Dir.glob(File.join(File.expand_path('.', __dir__), "#{example.underscore}*.rb")).select {|file| file.match(/#{example.underscore}\d\.rb$/)}.count + 1
end
def glimmer_dsl_libui_file
File.expand_path('../lib/glimmer-dsl-libui', __dir__)
end
def selected_example
examples[@selected_example_index]
end
def run_example(example)
Thread.new do
command = "#{RbConfig.ruby} -r #{glimmer_dsl_libui_file} #{example} 2>&1"
result = ''
IO.popen(command) do |f|
sleep(0.0001) # yield to main thread
f.each_line do |line|
result << line
puts line
$stdout.flush # for Windows
sleep(0.0001) # yield to main thread
end
end
Glimmer::LibUI.queue_main { msg_box('Error Running Example', result) } if result.downcase.include?('error')
end
end
def launch
window('Meta-Example', 700, 500) {
margined true
horizontal_box {
vertical_box {
stretchy false
tab {
stretchy false
tab_item('Basic') {
vertical_box {
@basic_example_radio_buttons = radio_buttons {
stretchy false
items basic_examples_with_versions
selected basic_examples_with_versions.index(examples_with_versions[@selected_example_index])
on_selected do
@selected_example_index = examples_with_versions.index(basic_examples_with_versions[@basic_example_radio_buttons.selected])
example = selected_example
self.code_text = File.read(file_path_for(example))
@version_spinbox.value = 1
end
}
label # filler
label # filler
}
}
tab_item('Advanced') {
vertical_box {
@advanced_example_radio_buttons = radio_buttons {
stretchy false
items advanced_examples_with_versions
on_selected do
@selected_example_index = examples_with_versions.index(advanced_examples_with_versions[@advanced_example_radio_buttons.selected])
example = selected_example
self.code_text = File.read(file_path_for(example))
@version_spinbox.value = 1
end
}
label # filler
label # filler
}
}
}
horizontal_box {
label('Version') {
stretchy false
}
@version_spinbox = spinbox(1, 100) {
value 1
on_changed do
example = selected_example
if @version_spinbox.value > version_count_for(example)
@version_spinbox.value -= 1
else
version_number = @version_spinbox.value == 1 ? '' : @version_spinbox.value
example = "#{selected_example}#{version_number}"
self.code_text = File.read(file_path_for(example))
end
end
}
}
horizontal_box {
stretchy false
button('Launch') {
on_clicked do
begin
parent_dir = File.join(Dir.home, '.glimmer-dsl-libui', 'examples')
FileUtils.mkdir_p(parent_dir)
example_file = File.join(parent_dir, "#{selected_example.underscore}.rb")
File.write(example_file, code_text)
example_supporting_directory = File.expand_path(selected_example.underscore, __dir__)
FileUtils.cp_r(example_supporting_directory, parent_dir) if Dir.exist?(example_supporting_directory)
FileUtils.cp_r(File.expand_path('../icons', __dir__), File.dirname(parent_dir))
FileUtils.cp_r(File.expand_path('../sounds', __dir__), File.dirname(parent_dir))
run_example(example_file)
rescue => e
puts e.full_message
puts 'Unable to write code changes! Running original example...'
run_example(file_path_for(selected_example))
end
end
}
button('Reset') {
on_clicked do
self.code_text = File.read(file_path_for(selected_example))
end
}
}
}
@code_entry = non_wrapping_multiline_entry {
text <=> [self, :code_text]
}
}
}.show
end
end
MetaExample.new.launch
Basic Examples
docs/examples/GLIMMER-DSL-LIBUI-BASIC-EXAMPLES.md
Mac | Windows | Linux |
---|---|---|
Advanced Examples
docs/examples/GLIMMER-DSL-LIBUI-ADVANCED-EXAMPLES.md
Mac | Windows | Linux |
---|---|---|
Applications
Here are some applications built with Glimmer DSL for LibUI
Manga2PDF
Download and merge manga images into a single pdf file.
https://github.com/PinGunter/manga2pdf
Befunge98 GUI
Ruby implementation of the Befunge-98 programmming language.
https://github.com/AndyObtiva/befunge98/tree/gui
i3off Gtk Ruby
https://github.com/iraamaro/i3off-gtk-ruby
Chess
https://github.com/mikeweber/chess
RubyCrumbler
NLP (Natural Language Processing) App
https://github.com/joh-ga/RubyCrumbler
Rubio-Radio
https://github.com/kojix2/rubio-radio
PMV Calc
https://github.com/bzanchet/pmv-calc
Suika Box
https://github.com/kojix2/suikabox
HTS Grid
https://github.com/kojix2/htsgrid
Process
Resources
Help
Issues
If you encounter issues that are not reported, discover missing features that are not mentioned in [TODO.md](TODO.md), or think up better ways to use libui than what is possible with Glimmer DSL for LibUI, you may submit an issue or pull request on GitHub. In the meantime, you may try older gem versions of Glimmer DSL for LibUI till you find one that works.
Chat
Planned Features and Feature Suggestions
These features have been planned or suggested. You might see them in a future version of Glimmer DSL for LibUI. You are welcome to contribute more feature suggestions.
[TODO.md](TODO.md)
Change Log
[CHANGELOG.md](CHANGELOG.md)
Contributing
If you would like to contribute to the project, please adhere to the Open-Source Etiquette to ensure the best results.
- Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
- Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
- Fork the project.
- Start a feature/bugfix branch.
- Commit and push until you are happy with your contribution.
- Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
- Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
Note that the latest development sometimes takes place in the development branch (usually deleted once merged back to master).
Contributors
- Andy Maleh (Founder)
Click here to view contributor commits.
License
[MIT](LICENSE.txt)
Copyright (c) 2021-2022 Andy Maleh
--
Built for Glimmer (DSL Framework).
*Note that all licence references and agreements mentioned in the Glimmer DSL for LibUI README section above
are relevant to that project's source code only.