# [](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.2.1
## Prerequisite-Free Ruby Desktop Development GUI Library
[![Gem Version](https://badge.fury.io/rb/glimmer-dsl-libui.svg)](http://badge.fury.io/rb/glimmer-dsl-libui)
[![Maintainability](https://api.codeclimate.com/v1/badges/ce2853efdbecf6ebdc73/maintainability)](https://codeclimate.com/github/AndyObtiva/glimmer-dsl-libui/maintainability)
[![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[Glimmer](https://github.com/AndyObtiva/glimmer) DSL for [LibUI](https://github.com/kojix2/LibUI) is a prerequisite-free Ruby desktop development GUI library. No need to pre-install any prerequisites. Just install the gem and have platform-independent native GUI that just works!
[LibUI](https://github.com/kojix2/LibUI) is a thin [Ruby](https://www.ruby-lang.org/en/) wrapper around [libui](https://github.com/andlabs/libui), a relatively new C GUI library that renders native controls on every platform (similar to [SWT](https://www.eclipse.org/swt/), but without the heavy weight of the [Java Virtual Machine](https://www.java.com/en/)).
The main trade-off in using [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) as opposed to [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) or [Glimmer DSL for Tk](https://github.com/AndyObtiva/glimmer-dsl-tk) is the fact that [SWT](https://www.eclipse.org/swt/) and [Tk](https://www.tcl.tk/) are more mature than mid-alpha [libui](https://github.com/andlabs/libui) as GUI toolkits. Still, if there is only a need to build a small simple application, [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) could be a good convenient choice due to having zero prerequisites beyond the dependencies included in the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui). Also, just like [Glimmer DSL for Tk](https://github.com/AndyObtiva/glimmer-dsl-tk), its apps start instantly and have a small memory footprint. [LibUI](https://github.com/kojix2/LibUI) is a promising new GUI toolkit that might prove quite worthy in the future.
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) aims to provide a DSL similar to the [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-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
- Bidirectional Data-Binding to declaratively wire and automatically synchronize GUI with Business Models
- Custom Control support
- Scaffolding for new custom controls, apps, and gems
- Native-Executable packaging on Mac, Windows, and Linux.
Hello, World!
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('hello world').show
```
![glimmer-dsl-libui-mac-basic-window.png](images/glimmer-dsl-libui-mac-basic-window.png)
![glimmer-dsl-libui-linux-basic-window.png](images/glimmer-dsl-libui-linux-basic-window.png)
Basic Table Progress Bar
```ruby
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] = [row_data[0], 100] # automatically updates table due to implicit data-binding
end
end
}
}
}.show
```
![glimmer-dsl-libui-mac-basic-table-progress-bar.png](images/glimmer-dsl-libui-mac-basic-table-progress-bar.png)
![glimmer-dsl-libui-linux-basic-table-progress-bar.png](images/glimmer-dsl-libui-linux-basic-table-progress-bar.png)
Area Gallery
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('Area Gallery', 400, 400) {
area {
path { # declarative stable path
square(0, 0, 100)
square(100, 100, 400)
fill r: 102, g: 102, b: 204
}
path { # declarative stable path
rectangle(0, 100, 100, 400)
rectangle(100, 0, 400, 100)
fill r: 204, g: 102, b: 204
}
path { # declarative stable path
figure(100, 100) {
line(100, 400)
line(400, 100)
line(400, 400)
closed true
}
fill r: 202, g: 102, b: 104, a: 0.5
stroke r: 0, g: 0, b: 0
}
path { # declarative stable path
figure(0, 0) {
bezier(200, 100, 100, 200, 400, 100)
bezier(300, 100, 100, 300, 100, 400)
bezier(100, 300, 300, 100, 400, 400)
closed true
}
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
}
path { # declarative stable path
circle(200, 200, 90)
fill r: 202, g: 102, b: 204, a: 0.5
stroke r: 0, g: 0, b: 0, thickness: 2
}
path { # declarative stable path
arc(400, 220, 180, 90, 90, false)
fill r: 204, 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
}
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
```
![glimmer-dsl-libui-mac-area-gallery.png](images/glimmer-dsl-libui-mac-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!](#examples)
NOTE: [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) is in early alpha mode (only supports included [examples](#examples)). Please help make better by contributing, adopting for small or low risk projects, and providing feedback. It is still an early alpha, so the more feedback and issues you report the better.
Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interested in:
- [glimmer-dsl-swt](https://github.com/AndyObtiva/glimmer-dsl-swt): Glimmer DSL for SWT (JRuby Desktop Development GUI Framework)
- [glimmer-dsl-opal](https://github.com/AndyObtiva/glimmer-dsl-opal): Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps)
- [glimmer-dsl-xml](https://github.com/AndyObtiva/glimmer-dsl-xml): Glimmer DSL for XML (& HTML)
- [glimmer-dsl-css](https://github.com/AndyObtiva/glimmer-dsl-css): Glimmer DSL for CSS
- [glimmer-dsl-tk](https://github.com/AndyObtiva/glimmer-dsl-tk): Glimmer DSL for Tk (MRI Ruby Desktop Development GUI Library)
## Table of Contents
- [Glimmer DSL for LibUI 0.2.1](#-glimmer-dsl-for-libui-021)
- [Glimmer GUI DSL Concepts](#glimmer-gui-dsl-concepts)
- [Usage](#usage)
- [Girb (Glimmer IRB)](#girb-glimmer-irb)
- [API](#api)
- [Supported Controls](#supported-controls)
- [Common Control Properties](#common-control-properties)
- [Common Control Operations](#common-control-operations)
- [LibUI Operations](#libui-operations)
- [Extra Dialogs](#extra-dialogs)
- [Extra Operations](#extra-operations)
- [Table API](#table-api)
- [Area API](#area-api)
- [Smart Defaults and Conventions](#smart-defaults-and-conventions)
- [API Gotchas](#api-gotchas)
- [Original API](#original-api)
- [Glimmer Style Guide](#glimmer-style-guide)
- [Examples](#examples)
- [Basic Window](#basic-window)
- [Basic Button](#basic-button)
- [Basic Entry](#basic-entry)
- [Simple Notepad](#simple-notepad)
- [Midi Player](#midi-player)
- [Control Gallery](#control-gallery)
- [Font Button](#font-button)
- [Color Button](#color-button)
- [Date Time Picker](#date-time-picker)
- [Grid](#grid)
- [Form](#form)
- [Basic Table](#basic-table)
- [Editable Table](#editable-table)
- [Editable Column Table](#editable-column-table)
- [Basic Table Image](#basic-table-image)
- [Basic Table Image Text](#basic-table-image-text)
- [Basic Table Button](#basic-table-button)
- [Basic Table Checkbox](#basic-table-checkbox)
- [Basic Table Checkbox Text](#basic-table-checkbox-text)
- [Basic Table Progress Bar](#basic-table-progress-bar)
- [Form Table](#form-table)
- [Basic Area](#basic-area)
- [Dynamic Area](#dynamic-area)
- [Area Gallery](#area-gallery)
- [Histogram](#histogram)
- [Basic Transform](#basic-transform)
- [Login](#login)
- [Timer](#timer)
- [Color The Circles](#color-the-circles)
- [Contributing to glimmer-dsl-libui](#contributing-to-glimmer-dsl-libui)
- [Help](#help)
- [Issues](#issues)
- [Chat](#chat)
- [Process](#process)
- [Planned Features and Feature Suggestions](#planned-features-and-feature-suggestions)
- [Change Log](#change-log)
- [Contributors](#contributors)
- [License](#license)
## Glimmer GUI DSL Concepts
The Glimmer GUI DSL provides object-oriented declarative hierarchical syntax for [LibUI](https://github.com/kojix2/LibUI) that:
- Supports smart defaults (e.g. automatic `on_closing` listener that quits `window`)
- Automates wiring of controls (e.g. `button` is automatically set as child of `window`)
- Hides lower-level details (e.g. `LibUI.main` loop is started automatically when triggering `show` on `window`)
- 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](https://github.com/kojix2/LibUI) syntax:
- **Control**: [LibUI](https://github.com/kojix2/LibUI) controls may be declared by lower-case underscored name (aka keyword) (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/Properties/Listeners Block**: Any keyword may be optionally followed by a Ruby curly-brace multi-line-block containing nested controls (content) and/or properties (attributes) (e.g. `window('hello world', 300, 200, true) {button('greet')}`). It optionally receives one arg representing the control (e.g. `button('greet') {|b| on_clicked { puts b.text}}`)
- **Property**: Control properties may be declared inside keyword blocks with lower-case underscored name followed by property value args (e.g. `title "hello world"` inside `group`). Behind the scenes, properties correspond to `control_set_property` methods.
- **Listener**: Control listeners may be declared inside keyword blocks with listener lower-case underscored name beginning with `on_` and receiving required block handler (e.g. `on_clicked {puts 'clicked'}` inside `button`). Behind the scenes, listeners correspond to `control_on_event` methods.
Example of an app written in [LibUI](https://github.com/kojix2/LibUI)'s procedural imperative syntax:
```ruby
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](https://github.com/AndyObtiva/glimmer) object-oriented declarative hierarchical syntax:
```ruby
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](https://rubygems.org/gems/glimmer-dsl-libui) gem directly:
```
gem install glimmer-dsl-libui
```
Or install via Bundler `Gemfile`:
```ruby
gem 'glimmer-dsl-libui', '~> 0.2.1'
```
Add `require 'glimmer-dsl-libui'` at the top, and then `include Glimmer` into the top-level main object for testing or into an actual class for serious usage.
Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
```ruby
require 'glimmer-dsl-libui'
class Application
include Glimmer
def launch
window('hello world', 300, 200) {
button('Button') {
on_clicked do
puts 'Button Clicked'
end
}
}.show
end
end
Application.new.launch
```
If you are new to [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui), check out [Girb](#girb-glimmer-irb) and [Examples](#examples) to quickly learn through copy/paste. You may refer to the [API](#api) later on once you have gotten your feet wet with [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-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)](#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](#glimmer-gui-dsl-concepts) 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`](#girb-glimmer-irb)):
```ruby
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 Fiddle pointer object. Ruby proxy objects rely on composition (via [Proxy Design Pattern](https://en.wikipedia.org/wiki/Proxy_pattern)) instead of inheritance to shield consumers from having to deal with lower-level details unless absolutely needed.
Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
```ruby
w = window('hello world') # => # #
```
### Supported Controls
Control(Args) | Properties | Listeners
------------- | ---------- | ---------
`about_menu_item` | None | `on_clicked`
`area` | None | `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
`bezier(c1_x as Numeric, c1_y as Numeric, c2_x as Numeric, c2_y as Numeric, end_x as Numeric, end_y as 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
`combobox` | `items` (`Array` of `String`), `selected` (`Integer`) | `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(width as Numeric, height as Numeric)` | None | 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
`label(text as String)` | `text` (`String`) | None
`line(x as Numeric, y as Numeric)` | `x` (`Numeric`), `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)` | `checked` (Boolean) | `on_clicked`
`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`
`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`), `stroke` (`Hash` of `:r` as `0`-`255`, `:g` as `0`-`255`, `:b` as `0`-`255`, `:a` as `0.0`-`1.0`, `:cap` as (`:round`, `:square`, `:flat`), `:join` as (`:miter`, `:round`, `:bevel`), `:thickness` as `Numeric`, `:miter_limit` as `Numeric`, `:dashes` as `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
`search_entry` | `read_only` (Boolean), `text` (`String`) | `on_changed`
`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
`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 | None
`text_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`), `fullscreen` (Boolean), `margined` (Boolean), `title` (`String`) | `on_closing`, `on_content_size_changed`, `on_destroy`
### Common Control Properties
- `enabled` (Boolean)
- `libui` (`Fiddle::Pointer`): returns wrapped [LibUI](https://github.com/kojix2/LibUI) object
- `parent_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](#glimmer-gui-dsl-concepts) when nested under `horizontal_box`, `vertical_box`, or `form`
- `left` [dsl-only] (`Integer`) [default=`0`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `grid`
- `top` [dsl-only] (`Integer`) [default=`0`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `grid`
- `xspan` [dsl-only] (`Integer`) [default=`1`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `grid`
- `yspan` [dsl-only] (`Integer`) [default=`1`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `grid`
- `hexpand` [dsl-only] (Boolean) [default=`false`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `grid`
- `halign` [dsl-only] (`:fill`, `:start`, `:center`, or `:end`) [default=`:fill`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `grid`
- `vexpand` [dsl-only] (Boolean) [default=`false`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `grid`
- `valign` [dsl-only] (`:fill`, `:start`, `:center`, or `:end`) [default=`:fill`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `grid`
### 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 possible
- `Glimmer::LibUI::timer(time_in_seconds=0.1, repeat: true, &block)`: calls block after time_in_seconds has elapsed, repeating indefinitely unless repeat is `false` or an `Integer` for finite number of repeats. Block can return `false` or `true` to override next repetition.
### Extra Dialogs
- `open_file(window as Glimmer::LibUI::WindowProxy)`: returns selected file (`String`) or `nil` if cancelled
- `save_file(window as Glimmer::LibUI::WindowProxy)`: returns selected file (`String`) or `nil` if cancelled
### Extra Operations
- `ControlProxy::control_proxies`: returns all instantiated control proxies in the application
- `ControlProxy::menu_proxies`: returns all instantiated `menu` proxies in the application
- `ControlProxy::image_proxies`: returns all instantiated `image` proxies in the application
- `ControlProxy::main_window_proxy`: returns the first window proxy instantiated in the application
- `ControlProxy#window_proxy`: returns the window proxy parent for a control
- `ControlProxy#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 Controls](#supported-controls)):
- `button_column`: expects `String` cell values
- `checkbox_column`: expects Boolean cell values
- `checkbox_text_column`: expects dual-element `Array` of Boolean and `String` cell values
- `image_column`: expects `image` cell values (produced by `image` and `image_part` keywords as per [Supported Controls](#supported-controls))
- `image_text_column`: expects dual-element `Array` of `image` and `String` cell values
- `text_column`: expects `String` cell values
- `progress_bar_column`: expects `Integer` 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/deletion `Array` method automatically deletes rows in actual `table` control
- Inserting cell rows: Calling `Array#<<`, `Array#push`, `Array#prepend`, or any insertion/addition `Array` method automatically inserts rows in actual `table` control
- Changing cell rows: Calling `Array#[]=`, `Array#map!`, or any update `Array` method automatically updates rows in actual `table` control
Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
```ruby
require 'glimmer-dsl-libui'
include Glimmer
data = [
['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
]
window('Contacts', 600, 600) { |w|
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.include?('')
msg_box_error(w, '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
@name_entry.text = ''
@email_entry.text = ''
@phone_entry.text = ''
@city_entry.text = ''
@state_entry.text = ''
end
end
}
table {
text_column('Name')
text_column('Email')
text_column('Phone')
text_column('City')
text_column('State')
cell_rows data # implicit data-binding
}
}
}.show
```
![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png)
Learn more by checking out [examples](#examples).
### 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 later on. Simply nest `path` and figures like `rectangle` and all drawing logic is generated automatically. Path proxy objects are preserved across redraws assuming there would be few stable paths (mostly for decorative reasons).
- Semi-declaratively via on_draw listener dynamic paths: useful for more dynamic paths that will definitely change. Open an `on_draw` listener block that receives a `area_draw_params` argument and nest `path` and figures like `rectangle` 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.
Here is an example of a declarative `area` with a stable path (you may copy/paste in [`girb`](#girb-glimmer-irb)):
```ruby
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
```
![glimmer-dsl-libui-mac-basic-area.png](images/glimmer-dsl-libui-mac-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`](#girb-glimmer-irb)):
```ruby
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](#dynamic-area) for a more detailed semi-declarative example.
`path` can receive a `draw_fill_mode` argument that can accept values `:winding` or `:alternate` and defaults to `:winding`.
Available nested `path` shapes:
- `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)`
- `line(x as Numeric, y as Numeric)`
- `bezier(c1_x as Numeric, c1_y as Numeric, c2_x as Numeric, c2_y as Numeric, end_x as Numeric, end_y as Numeric)`
- `figure(x=nil as Numeric, y=nil as Numeric)` (composite that can contain other shapes) (can set `closed true` to connect last point to first point automatically)
Check [examples/area_gallery.rb](#area-gallery) for an overiew of all `path` shapes.
The `area_draw_params` 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` supported mouse 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 what `on_mouse_entered` and `on_mouse_exited` do by returning a `left` argument indicating if mouse left `area`)
- `on_drag_broken {...}` (NOT RECOMMENDED; varies per platforms; use `on_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` of `Symbol`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`: key value (`Integer`). Useful in rare cases for numeric processing of keys instead of dealing with as `:key` character `String`
- `:ext_key`: non-character extra key (`Symbol`) from `Glimmer::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)
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.
To redraw an `area`, you may call the `#queue_redraw_all` method, or simply `#redraw`.
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 matrix
- `translate(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`](#girb-glimmer-irb)):
```ruby
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:
```ruby
transform {
translate 100, 100
rotate 100, 100, -9 * n
}
```
Alternatively:
```ruby
m1 = matrix {
translate 100, 100
rotate 100, 100, -9 * n
}
transform m1
# and then reuse m1 elsewhere 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.
`fill` and `stroke` accept [X11](https://en.wikipedia.org/wiki/X11_color_names) color `Symbol`s/`String`s like `:skyblue` and `'sandybrown'` or 6-number hex or 3-number hex-shorthand (as `Integer` or `String` with or without `0x` prefix)
Available [X11](https://en.wikipedia.org/wiki/X11_color_names) colors can be obtained through `Glimmer::LibUI.x11_colors` method.
Check [Basic Transform](#basic-transform) example for use of [X11](https://en.wikipedia.org/wiki/X11_color_names) colors.
Check [Histogram](#histogram) example for use of hex colors.
### Smart Defaults and Conventions
- `horizontal_box`, `vertical_box`, `grid`, and `form` controls have `padded` as `true` upon instantiation to ensure more user-friendly GUI by default
- `group` controls have `margined` as `true` upon instantiation to ensure more user-friendly GUI by default
- All controls nested under a `horizontal_box`, `vertical_box`, and `form` have `stretchy` property (fill maximum space) as `true` by default (passed to `box_append`/`form_append` method)
- `window` instatiation args can be left off, having the following defaults when unspecified: `title` as `''`, `width` as `190`, `height` as `150`, and `has_menubar` as `true`)
- `window` has an `on_closing` listener by default that quits application upon hitting the close button (can be overridden with a manual `on_closing` implementation that returns integer `0` for success)
- `group` has `title` property default to `''` if not specified in instantiation args, so it can be instantiated without args with `title` property specified in nested block (e.g. `group {title 'Address'; ...}`)
- `button`, `checkbox`, and `label` have `text` default to `''` if not specified in instantiation args, so they can be instantiated without args with `text` property specified in nested block (e.g. `button {text 'Greet'; on_clicked {puts 'Hello'}}`)
- `quit_menu_item` has an `on_clicked` listener by default that quits application upon selecting the quit menu item (can be overridden with a manual `on_clicked` implementation that returns integer `0` for success)
- If an `on_closing` listener was defined on `window` and it does not return an integer, default exit behavior is assumed (`window.destroy` is called followed by `LibUI.quit`, returning `0`).
- If an `on_clicked` listener was defined on `quit_menu_item` and it does not return an integer, default exit behavior is assumed (`main_window.destroy` is called followed by `LibUI.quit`, returning `0`).
- All boolean property readers return `true` or `false` in Ruby instead of the [libui](https://github.com/andlabs/libui) original `0` or `1` in C.
- All boolean property writers accept `true`/`false` in addition to `1`/`0` in Ruby
- All string property readers return a `String` object in Ruby instead of the [libui](https://github.com/andlabs/libui) Fiddle pointer object.
- Automatically allocate font descriptors upon instantiating `font_button` controls and free them when destorying `font_button` controls
- Automatically allocate color value pointers upon instantiating `color_button` controls and free them when destorying `color_button` controls
- On the Mac, if no `menu` items were added, an automatic `quit_menu_item` is added to enable quitting with CTRL+Q
- When destroying a control nested under a `horizontal_box` or `vertical_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` or `group`, it is automatically unset as their child to allow successful destruction
- For `date_time_picker`, `date_picker`, and `time_picker`, make sure `time` hash values for `mon`, `wday`, and `yday` are 1-based instead of [libui](https://github.com/andlabs/libui) original 0-based values, and return `dst` as Boolean instead of `isdst` as `1`/`0`
- Smart defaults for `grid` child attributes are `left` (`0`), `top` (`0`), `xspan` (`1`), `yspan` (`1`), `hexpand` (`false`), `halign` (`:fill`), `vexpand` (`false`), and `valign` (`:fill`)
- The `table` control automatically constructs required `TableModelHandler`, `TableModel`, and `TableParams`, calculating all their arguments from `cell_rows` and `editable` 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 diffing `cell_rows` value before and after change and auto-informing `table` of deletions [`LibUI.table_model_row_deleted`], insertions [`LibUI.table_model_row_deleted`], and changes [`LibUI.table_model_row_changed`]). When deleting data rows from `cell_rows` array, then actual rows from the `table` are automatically deleted. When inserting data rows into `cell_rows` array, then actual `table` rows are automatically inserted. When updating data rows in `cell_rows` array, then actual `table` rows are automatically updated.
- `image` instances are automatically freed from memory after `window` is destroyed.
- `image` `width` and `height` can be left off if it has one `image_part` only as they default to the same `width` and `height` of the `image_part`
- `area` paths are specified declaratively with figures underneath (e.g. `rectangle`) and `area` draw listener is automatically generated
- Observe figure properties (e.g. `rectangle` `width`) for changes and automatically redraw containing area accordingly
- Observe `path` `fill` and `stroke` hashes 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 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.
- Colors may be passed in as a hash of `:r`, `:g`, `:b`, `:a`, or `:red`, `:green`, `:blue`, `:alpha`, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color like `:skyblue`, or 6-number hex or 3-number hex (as `Integer` or `String` with or without `0x` prefix)
- Color alpha value defaults to `1.0` when not specified.
### API Gotchas
- There is no proper way to destroy `grid` children due to [libui](https://github.com/andlabs/libui) not offering any API for deleting them from `grid` (no `grid_delete` similar to `box_delete` for `horizontal_box` and `vertical_box`).
- `table` `checkbox_column` and `checkbox_text_column` checkbox editing only works on Windows and Linux (not Mac) due to a current limitation in [libui](https://github.com/andlabs/ui/issues/357).
- It seems that `arc` `start_angle` and `sweep` properties are ignored by [libui](https://github.com/andlabs/libui) and always set to `0` and `360` respectively, producing a full circle.
### Original API
To learn more about the [LibUI](https://github.com/kojix2/LibUI) API exposed through [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui),
check out the [libui C headers](https://github.com/andlabs/libui/blob/master/ui.h)
## Glimmer Style Guide
- Control arguments are always wrapped by parentheses
- Control blocks are always declared with curly braces to clearly visualize hierarchical view code and separate from logic code
- Control property declarations always have arguments and never take a block
- Control property arguments are never wrapped inside parentheses
- 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.
- Pure logic multi-line blocks that do not constitute GUI DSL view elements have `do; end` style to clearly separate logic code from view code.
## Examples
The following examples include reimplementions of the examples in the [LibUI](https://github.com/kojix2/LibUI) project utilizing the [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) 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.
(note that for examples that emit output to terminal/command-line via `p` or `puts`, you must run them directly to see output)
[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](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/meta_example'"
```
Mac
![glimmer-dsl-libui-mac-meta-example.png](images/glimmer-dsl-libui-mac-meta-example.png)
Linux
![glimmer-dsl-libui-linux-meta-example.png](images/glimmer-dsl-libui-linux-meta-example.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
require 'facets'
class MetaExample
include Glimmer
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' }
@examples = example_file_names.map { |f| f.underscore.titlecase }
end
@examples
end
def file_path_for(example)
File.join(File.expand_path('.', __dir__), "#{example.underscore}.rb")
end
def glimmer_dsl_libui_file
File.expand_path('../lib/glimmer-dsl-libui', __dir__)
end
def launch
window('Meta-Example', 700, 500) {
margined true
horizontal_box {
vertical_box {
@rbs = radio_buttons {
stretchy false
items examples
selected 0
on_selected do
@nwme.text = File.read(file_path_for(@examples[@rbs.selected]))
end
}
button('Launch') {
stretchy false
on_clicked do
begin
meta_example_file = File.join(Dir.home, '.meta_example.rb')
File.write(meta_example_file, @nwme.text)
result = `ruby -r #{glimmer_dsl_libui_file} #{meta_example_file} 2>&1`
msg_box('Error Running Example', result) if result.include?('error')
rescue => e
puts 'Unable to write code changes! Running original example...'
system "ruby -r #{glimmer_dsl_libui_file} #{file_path_for(@examples[@rbs.selected])}"
end
end
}
}
vertical_box {
@nwme = non_wrapping_multiline_entry {
text File.read(file_path_for(@examples[@rbs.selected]))
}
}
}
}.show
end
end
MetaExample.new.launch
```
### Basic Window
[examples/basic_window.rb](examples/basic_window.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/basic_window.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/basic_window'"
```
Mac
![glimmer-dsl-libui-mac-basic-window.png](images/glimmer-dsl-libui-mac-basic-window.png)
Linux
![glimmer-dsl-libui-linux-basic-window.png](images/glimmer-dsl-libui-linux-basic-window.png)
[LibUI](https://github.com/kojix2/LibUI) Original Version:
```ruby
require 'libui'
UI = LibUI
UI.init
main_window = UI.new_window('hello world', 300, 200, 1)
UI.control_show(main_window)
UI.window_on_closing(main_window) do
puts 'Bye Bye'
UI.control_destroy(main_window)
UI.quit
0
end
UI.main
UI.quit
```
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('hello world', 300, 200, true) {
on_closing do
puts 'Bye Bye'
end
}.show
```
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (setting `window` properties instead of arguments):
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window { # first 3 args can be set via properties with 4th arg has_menubar=true by default
title 'hello world'
content_size 300, 200
on_closing do
puts 'Bye Bye'
end
}.show
```
### Basic Button
[examples/basic_button.rb](examples/basic_button.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/basic_button.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/basic_button'"
```
Mac
![glimmer-dsl-libui-mac-basic-button.png](images/glimmer-dsl-libui-mac-basic-button.png)
![glimmer-dsl-libui-mac-basic-button-msg-box.png](images/glimmer-dsl-libui-mac-basic-button-msg-box.png)
Linux
![glimmer-dsl-libui-linux-basic-button.png](images/glimmer-dsl-libui-linux-basic-button.png)
![glimmer-dsl-libui-linux-basic-button-msg-box.png](images/glimmer-dsl-libui-linux-basic-button-msg-box.png)
[LibUI](https://github.com/kojix2/LibUI) Original Version:
```ruby
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
```
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
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
```
### Basic Entry
[examples/basic_entry.rb](examples/basic_entry.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/basic_entry.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/basic_entry'"
```
Mac
![glimmer-dsl-libui-mac-basic-entry.png](images/glimmer-dsl-libui-mac-basic-entry.png)
![glimmer-dsl-libui-mac-basic-entry-msg-box.png](images/glimmer-dsl-libui-mac-basic-entry-msg-box.png)
Linux
![glimmer-dsl-libui-linux-basic-entry.png](images/glimmer-dsl-libui-linux-basic-entry.png)
![glimmer-dsl-libui-linux-basic-entry-msg-box.png](images/glimmer-dsl-libui-linux-basic-entry-msg-box.png)
[LibUI](https://github.com/kojix2/LibUI) Original Version:
```ruby
require 'libui'
UI = LibUI
UI.init
main_window = UI.new_window('Basic Entry', 300, 50, 1)
UI.window_on_closing(main_window) do
puts 'Bye Bye'
UI.control_destroy(main_window)
UI.quit
0
end
hbox = UI.new_horizontal_box
UI.window_set_child(main_window, hbox)
entry = UI.new_entry
UI.entry_on_changed(entry) do
puts UI.entry_text(entry).to_s
$stdout.flush # For Windows
end
UI.box_append(hbox, entry, 1)
button = UI.new_button('Button')
UI.button_on_clicked(button) do
text = UI.entry_text(entry).to_s
UI.msg_box(main_window, 'You entered', text)
0
end
UI.box_append(hbox, button, 0)
UI.control_show(main_window)
UI.main
UI.quit
```
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('Basic Entry', 300, 50) {
horizontal_box {
e = entry {
# stretchy true # Smart default option for appending to horizontal_box
on_changed do
puts e.text
$stdout.flush # For Windows
end
}
button('Button') {
stretchy false # stretchy property is available when control is nested under horizontal_box
on_clicked do
text = e.text
msg_box('You entered', text)
end
}
}
on_closing do
puts 'Bye Bye'
end
}.show
```
### Simple Notepad
[examples/simple_notepad.rb](examples/simple_notepad.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/simple_notepad.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/simple_notepad'"
```
Mac
![glimmer-dsl-libui-mac-simple-notepad.png](images/glimmer-dsl-libui-mac-simple-notepad.png)
Linux
![glimmer-dsl-libui-linux-simple-notepad.png](images/glimmer-dsl-libui-linux-simple-notepad.png)
[LibUI](https://github.com/kojix2/LibUI) Original Version:
```ruby
require 'libui'
UI = LibUI
UI.init
main_window = UI.new_window('Notepad', 500, 300, 1)
UI.window_on_closing(main_window) do
puts 'Bye Bye'
UI.control_destroy(main_window)
UI.quit
0
end
vbox = UI.new_vertical_box
UI.window_set_child(main_window, vbox)
entry = UI.new_non_wrapping_multiline_entry
UI.box_append(vbox, entry, 1)
UI.control_show(main_window)
UI.main
UI.quit
```
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('Notepad', 500, 300) {
on_closing do
puts 'Bye Bye'
end
vertical_box {
non_wrapping_multiline_entry
}
}.show
```
### Midi Player
This example has prerequisites:
- Install [TiMidity](http://timidity.sourceforge.net) and ensure `timidity` command is in `PATH` (can be installed via [Homebrew](https://brew.sh) on Mac or [apt-get](https://help.ubuntu.com/community/AptGet/Howto) on Linux).
- Add `*.mid` files to `~/Music` directory (you may copy the ones included in [sounds](sounds) directory)
[examples/midi_player.rb](examples/midi_player.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/midi_player.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/midi_player'"
```
Mac
![glimmer-dsl-libui-mac-midi-player.png](images/glimmer-dsl-libui-mac-midi-player.png)
![glimmer-dsl-libui-mac-midi-player-msg-box.png](images/glimmer-dsl-libui-mac-midi-player-msg-box.png)
Linux
![glimmer-dsl-libui-linux-midi-player.png](images/glimmer-dsl-libui-linux-midi-player.png)
![glimmer-dsl-libui-linux-midi-player-msg-box.png](images/glimmer-dsl-libui-linux-midi-player-msg-box.png)
[LibUI](https://github.com/kojix2/LibUI) Original Version:
```ruby
require 'libui'
UI = LibUI
class TinyMidiPlayer
VERSION = '0.0.1'
def initialize
UI.init
@pid = nil
@music_directory = File.expand_path(ARGV[0] || '~/Music/')
@midi_files = Dir.glob(File.join(@music_directory, '**/*.mid'))
.sort_by { |path| File.basename(path) }
at_exit { stop_midi }
create_gui
end
def stop_midi
if @pid
if @th.alive?
Process.kill(:SIGKILL, @pid)
@pid = nil
else
@pid = nil
end
end
end
def play_midi
stop_midi
if @pid.nil? && @selected_file
begin
@pid = spawn "timidity #{@selected_file}"
@th = Process.detach @pid
rescue Errno::ENOENT
warn 'Timidty++ not found. Please install Timidity++.'
warn 'https://sourceforge.net/projects/timidity/'
end
end
end
def show_version(main_window)
UI.msg_box(main_window,
'Tiny Midi Player',
"Written in Ruby\n" \
"https://github.com/kojix2/libui\n" \
"Version #{VERSION}")
end
def create_gui
help_menu = UI.new_menu('Help')
version_item = UI.menu_append_item(help_menu, 'Version')
UI.new_window('Tiny Midi Player', 200, 50, 1).tap do |main_window|
UI.menu_item_on_clicked(version_item) { show_version(main_window) }
UI.window_on_closing(main_window) do
UI.control_destroy(main_window)
UI.quit
0
end
UI.new_horizontal_box.tap do |hbox|
UI.new_vertical_box.tap do |vbox|
UI.new_button('▶').tap do |button1|
UI.button_on_clicked(button1) { play_midi }
UI.box_append(vbox, button1, 1)
end
UI.new_button('■').tap do |button2|
UI.button_on_clicked(button2) { stop_midi }
UI.box_append(vbox, button2, 1)
end
UI.box_append(hbox, vbox, 0)
end
UI.window_set_child(main_window, hbox)
UI.new_combobox.tap do |cbox|
@midi_files.each do |path|
name = File.basename(path)
UI.combobox_append(cbox, name)
end
UI.combobox_on_selected(cbox) do |ptr|
@selected_file = @midi_files[UI.combobox_selected(ptr)]
play_midi if @th&.alive?
0
end
UI.box_append(hbox, cbox, 1)
end
end
UI.control_show(main_window)
end
UI.main
UI.quit
end
end
TinyMidiPlayer.new
```
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
class TinyMidiPlayer
include Glimmer
VERSION = '0.0.1'
def initialize
@pid = nil
@music_directory = File.expand_path(ARGV[0] || '~/Music/')
@midi_files = Dir.glob(File.join(@music_directory, '**/*.mid'))
.sort_by { |path| File.basename(path) }
at_exit { stop_midi }
create_gui
end
def stop_midi
if @pid
if @th.alive?
Process.kill(:SIGKILL, @pid)
@pid = nil
else
@pid = nil
end
end
end
def play_midi
stop_midi
if @pid.nil? && @selected_file
begin
@pid = spawn "timidity #{@selected_file}"
@th = Process.detach @pid
rescue Errno::ENOENT
warn 'Timidty++ not found. Please install Timidity++.'
warn 'https://sourceforge.net/projects/timidity/'
end
end
end
def show_version
msg_box('Tiny Midi Player',
"Written in Ruby\n" \
"https://github.com/kojix2/libui\n" \
"Version #{VERSION}")
end
def create_gui
menu('Help') {
menu_item('Version') {
on_clicked do
show_version
end
}
}
window('Tiny Midi Player', 200, 50) {
horizontal_box {
vertical_box {
stretchy false
button('▶') {
on_clicked do
play_midi
end
}
button('■') {
on_clicked do
stop_midi
end
}
}
combobox { |c|
items @midi_files.map { |path| File.basename(path) }
on_selected do
@selected_file = @midi_files[c.selected]
play_midi if @th&.alive?
end
}
}
}.show
end
end
TinyMidiPlayer.new
```
### Control Gallery
[examples/control_gallery.rb](examples/control_gallery.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/control_gallery.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/control_gallery'"
```
Mac
![glimmer-dsl-libui-mac-control-gallery.png](images/glimmer-dsl-libui-mac-control-gallery.png)
Linux
![glimmer-dsl-libui-linux-control-gallery.png](images/glimmer-dsl-libui-linux-control-gallery.png)
[LibUI](https://github.com/kojix2/LibUI) Original Version:
```ruby
require 'libui'
UI = LibUI
UI.init
should_quit = proc do
puts 'Bye Bye'
UI.control_destroy(MAIN_WINDOW)
UI.quit
0
end
# File menu
menu = UI.new_menu('File')
open_menu_item = UI.menu_append_item(menu, 'Open')
UI.menu_item_on_clicked(open_menu_item) do
pt = UI.open_file(MAIN_WINDOW)
puts pt unless pt.null?
end
save_menu_item = UI.menu_append_item(menu, 'Save')
UI.menu_item_on_clicked(save_menu_item) do
pt = UI.save_file(MAIN_WINDOW)
puts pt unless pt.null?
end
UI.menu_append_quit_item(menu)
UI.on_should_quit(should_quit)
# Edit menu
edit_menu = UI.new_menu('Edit')
UI.menu_append_check_item(edit_menu, 'Checkable Item_')
UI.menu_append_separator(edit_menu)
disabled_item = UI.menu_append_item(edit_menu, 'Disabled Item_')
UI.menu_item_disable(disabled_item)
preferences = UI.menu_append_preferences_item(menu)
# Help menu
help_menu = UI.new_menu('Help')
UI.menu_append_item(help_menu, 'Help')
UI.menu_append_about_item(help_menu)
# Main Window
MAIN_WINDOW = UI.new_window('Control Gallery', 600, 500, 1)
UI.window_set_margined(MAIN_WINDOW, 1)
UI.window_on_closing(MAIN_WINDOW, should_quit)
vbox = UI.new_vertical_box
UI.window_set_child(MAIN_WINDOW, vbox)
hbox = UI.new_horizontal_box
UI.box_set_padded(vbox, 1)
UI.box_set_padded(hbox, 1)
UI.box_append(vbox, hbox, 1)
# Group - Basic Controls
group = UI.new_group('Basic Controls')
UI.group_set_margined(group, 1)
UI.box_append(hbox, group, 1) # OSX bug?
inner = UI.new_vertical_box
UI.box_set_padded(inner, 1)
UI.group_set_child(group, inner)
# Button
button = UI.new_button('Button')
UI.button_on_clicked(button) do
UI.msg_box(MAIN_WINDOW, 'Information', 'You clicked the button')
end
UI.box_append(inner, button, 0)
# Checkbox
checkbox = UI.new_checkbox('Checkbox')
UI.checkbox_on_toggled(checkbox) do |ptr|
checked = UI.checkbox_checked(ptr) == 1
UI.window_set_title(MAIN_WINDOW, "Checkbox is #{checked}")
UI.checkbox_set_text(ptr, "I am the checkbox (#{checked})")
end
UI.box_append(inner, checkbox, 0)
# Label
UI.box_append(inner, UI.new_label('Label'), 0)
# Separator
UI.box_append(inner, UI.new_horizontal_separator, 0)
# Date Picker
UI.box_append(inner, UI.new_date_picker, 0)
# Time Picker
UI.box_append(inner, UI.new_time_picker, 0)
# Date Time Picker
UI.box_append(inner, UI.new_date_time_picker, 0)
# Font Button
UI.box_append(inner, UI.new_font_button, 0)
# Color Button
UI.box_append(inner, UI.new_color_button, 0)
inner2 = UI.new_vertical_box
UI.box_set_padded(inner2, 1)
UI.box_append(hbox, inner2, 1)
# Group - Numbers
group = UI.new_group('Numbers')
UI.group_set_margined(group, 1)
UI.box_append(inner2, group, 0)
inner = UI.new_vertical_box
UI.box_set_padded(inner, 1)
UI.group_set_child(group, inner)
# Spinbox
spinbox = UI.new_spinbox(0, 100)
UI.spinbox_set_value(spinbox, 42)
UI.spinbox_on_changed(spinbox) do |ptr|
puts "New Spinbox value: #{UI.spinbox_value(ptr)}"
end
UI.box_append(inner, spinbox, 0)
# Slider
slider = UI.new_slider(0, 100)
UI.box_append(inner, slider, 0)
# Progressbar
progressbar = UI.new_progress_bar
UI.box_append(inner, progressbar, 0)
UI.slider_on_changed(slider) do |ptr|
v = UI.slider_value(ptr)
puts "New Slider value: #{v}"
UI.progress_bar_set_value(progressbar, v)
end
# Group - Lists
group = UI.new_group('Lists')
UI.group_set_margined(group, 1)
UI.box_append(inner2, group, 0)
inner = UI.new_vertical_box
UI.box_set_padded(inner, 1)
UI.group_set_child(group, inner)
# Combobox
cbox = UI.new_combobox
UI.combobox_append(cbox, 'combobox Item 1')
UI.combobox_append(cbox, 'combobox Item 2')
UI.combobox_append(cbox, 'combobox Item 3')
UI.box_append(inner, cbox, 0)
UI.combobox_on_selected(cbox) do |ptr|
puts "New combobox selection: #{UI.combobox_selected(ptr)}"
end
# Editable Combobox
ebox = UI.new_editable_combobox
UI.editable_combobox_append(ebox, 'Editable Item 1')
UI.editable_combobox_append(ebox, 'Editable Item 2')
UI.editable_combobox_append(ebox, 'Editable Item 3')
UI.box_append(inner, ebox, 0)
# Radio Buttons
rb = UI.new_radio_buttons
UI.radio_buttons_append(rb, 'Radio Button 1')
UI.radio_buttons_append(rb, 'Radio Button 2')
UI.radio_buttons_append(rb, 'Radio Button 3')
UI.box_append(inner, rb, 1)
# Tab
tab = UI.new_tab
hbox1 = UI.new_horizontal_box
hbox2 = UI.new_horizontal_box
UI.tab_append(tab, 'Page 1', hbox1)
UI.tab_append(tab, 'Page 2', hbox2)
UI.tab_append(tab, 'Page 3', UI.new_horizontal_box)
UI.box_append(inner2, tab, 1)
# Text Entry
text_entry = UI.new_entry
UI.entry_set_text text_entry, 'Please enter your feelings'
UI.entry_on_changed(text_entry) do |ptr|
puts "Current textbox data: '#{UI.entry_text(ptr)}'"
end
UI.box_append(hbox1, text_entry, 1)
UI.control_show(MAIN_WINDOW)
UI.main
UI.quit
```
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
menu('File') {
menu_item('Open') {
on_clicked do
file = open_file
puts file unless file.nil?
end
}
menu_item('Save') {
on_clicked do
file = save_file
puts file unless file.nil?
end
}
quit_menu_item {
on_clicked do
puts 'Bye Bye'
end
}
preferences_menu_item # Can optionally contain an on_clicked listener
}
menu('Edit') {
check_menu_item('Checkable Item_')
separator_menu_item
menu_item('Disabled Item_') {
enabled false
}
}
menu('Help') {
menu_item('Help')
about_menu_item # Can optionally contain an on_clicked listener
}
MAIN_WINDOW = window('Control Gallery', 600, 500) {
margined true
on_closing do
puts 'Bye Bye'
end
vertical_box {
horizontal_box {
group('Basic Controls') {
vertical_box {
button('Button') {
stretchy false
on_clicked do
msg_box('Information', 'You clicked the button')
end
}
checkbox('Checkbox') {
stretchy false
on_toggled do |c|
checked = c.checked?
MAIN_WINDOW.title = "Checkbox is #{checked}"
c.text = "I am the checkbox (#{checked})"
end
}
label('Label') { stretchy false }
horizontal_separator { stretchy false }
date_picker { stretchy false }
time_picker { stretchy false }
date_time_picker { stretchy false }
font_button { stretchy false }
color_button { stretchy false }
}
}
vertical_box {
group('Numbers') {
stretchy false
vertical_box {
spinbox(0, 100) {
stretchy false
value 42
on_changed do |s|
puts "New Spinbox value: #{s.value}"
end
}
slider(0, 100) {
stretchy false
on_changed do |s|
v = s.value
puts "New Slider value: #{v}"
@progress_bar.value = v
end
}
@progress_bar = progress_bar { stretchy false }
}
}
group('Lists') {
stretchy false
vertical_box {
combobox {
stretchy false
items 'combobox Item 1', 'combobox Item 2', 'combobox Item 3' # also accepts a single array argument
on_selected do |c|
puts "New combobox selection: #{c.selected}"
end
}
editable_combobox {
stretchy false
items 'Editable Item 1', 'Editable Item 2', 'Editable Item 3' # also accepts a single array argument
}
radio_buttons {
items 'Radio Button 1', 'Radio Button 2', 'Radio Button 3' # also accepts a single array argument
}
}
}
tab {
tab_item('Page 1') {
horizontal_box {
entry {
text 'Please enter your feelings'
on_changed do |e|
puts "Current textbox data: '#{e.text}'"
end
}
}
}
tab_item('Page 2') {
horizontal_box
}
tab_item('Page 3') {
horizontal_box
}
}
}
}
}
}
MAIN_WINDOW.show
```
### Font Button
[examples/font_button.rb](examples/font_button.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/font_button.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/font_button'"
```
Mac
![glimmer-dsl-libui-mac-font-button.png](images/glimmer-dsl-libui-mac-font-button.png)
![glimmer-dsl-libui-mac-font-button-selection.png](images/glimmer-dsl-libui-mac-font-button-selection.png)
Linux
![glimmer-dsl-libui-linux-font-button.png](images/glimmer-dsl-libui-linux-font-button.png)
![glimmer-dsl-libui-linux-font-button-selection.png](images/glimmer-dsl-libui-linux-font-button-selection.png)
[LibUI](https://github.com/kojix2/LibUI) Original Version:
```ruby
require 'libui'
UI = LibUI
UI.init
main_window = UI.new_window('hello world', 300, 200, 1)
font_button = UI.new_font_button
font_descriptor = UI::FFI::FontDescriptor.malloc
UI.font_button_on_changed(font_button) do
UI.font_button_font(font_button, font_descriptor)
p family: font_descriptor.Family.to_s,
size: font_descriptor.Size,
weight: font_descriptor.Weight,
italic: font_descriptor.Italic,
stretch: font_descriptor.Stretch
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, font_button)
UI.control_show(main_window)
UI.main
UI.quit
```
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('hello world', 300, 200) {
font_button { |fb|
on_changed do
font_descriptor = fb.font
p font_descriptor
end
}
on_closing do
puts 'Bye Bye'
end
}.show
```
### Color Button
[examples/color_button.rb](examples/color_button.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/color_button.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/color_button'"
```
Mac
![glimmer-dsl-libui-mac-color-button.png](images/glimmer-dsl-libui-mac-color-button.png)
![glimmer-dsl-libui-mac-color-button-selection.png](images/glimmer-dsl-libui-mac-color-button-selection.png)
Linux
![glimmer-dsl-libui-linux-color-button.png](images/glimmer-dsl-libui-linux-color-button.png)
![glimmer-dsl-libui-linux-color-button-selection.png](images/glimmer-dsl-libui-linux-color-button-selection.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('color button', 230) {
color_button { |cb|
on_changed do
rgba = cb.color
p rgba
end
}
}.show
```
### Date Time Picker
[examples/date_time_picker.rb](examples/date_time_picker.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/date_time_picker.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/date_time_picker'"
```
Mac
![glimmer-dsl-libui-mac-date-time-picker.png](images/glimmer-dsl-libui-mac-date-time-picker.png)
Linux
![glimmer-dsl-libui-linux-date-time-picker.png](images/glimmer-dsl-libui-linux-date-time-picker.png)
[LibUI](https://github.com/kojix2/LibUI) Original Version:
```ruby
require 'libui'
UI = LibUI
UI.init
vbox = UI.new_vertical_box
date_time_picker = UI.new_date_time_picker
time = UI::FFI::TM.malloc
UI.date_time_picker_on_changed(date_time_picker) do
UI.date_time_picker_time(date_time_picker, time)
p sec: time.tm_sec,
min: time.tm_min,
hour: time.tm_hour,
mday: time.tm_mday,
mon: time.tm_mon,
year: time.tm_year,
wday: time.tm_wday,
yday: time.tm_yday,
isdst: time.tm_isdst
end
UI.box_append(vbox, date_time_picker, 1)
main_window = UI.new_window('Date Time Pickers', 300, 200, 1)
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, vbox)
UI.control_show(main_window)
UI.main
UI.quit
```
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('Date Time Pickers', 300, 200) {
vertical_box {
date_time_picker { |dtp|
on_changed do
time = dtp.time
p time
end
}
}
on_closing do
puts 'Bye Bye'
end
}.show
```
### Grid
[examples/grid.rb](examples/grid.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/grid.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/grid'"
```
Mac
![glimmer-dsl-libui-mac-grid-span.png](images/glimmer-dsl-libui-mac-grid-span.png)
![glimmer-dsl-libui-mac-grid-expand.png](images/glimmer-dsl-libui-mac-grid-expand.png)
![glimmer-dsl-libui-mac-grid-align.png](images/glimmer-dsl-libui-mac-grid-align.png)
Linux
![glimmer-dsl-libui-linux-grid-span.png](images/glimmer-dsl-libui-linux-grid-span.png)
![glimmer-dsl-libui-linux-grid-expand.png](images/glimmer-dsl-libui-linux-grid-expand.png)
![glimmer-dsl-libui-linux-grid-align.png](images/glimmer-dsl-libui-linux-grid-align.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('Grid') {
tab {
tab_item('Span') {
grid {
4.times { |top_value|
4.times { |left_value|
label("(#{left_value}, #{top_value}) xspan1\nyspan1") {
left left_value
top top_value
hexpand true
vexpand true
}
}
}
label("(0, 4) xspan2\nyspan1 more text fits horizontally") {
left 0
top 4
xspan 2
}
label("(2, 4) xspan2\nyspan1 more text fits horizontally") {
left 2
top 4
xspan 2
}
label("(0, 5) xspan1\nyspan2\nmore text\nfits vertically") {
left 0
top 5
yspan 2
}
label("(0, 7) xspan1\nyspan2\nmore text\nfits vertically") {
left 0
top 7
yspan 2
}
label("(1, 5) xspan3\nyspan4 a lot more text fits horizontally than before\nand\neven\na lot\nmore text\nfits vertically\nthan\nbefore") {
left 1
top 5
xspan 3
yspan 4
}
}
}
tab_item('Expand') {
grid {
label("(0, 0) hexpand/vexpand\nall available horizontal space is taken\nand\nall\navailable\nvertical\nspace\nis\ntaken") {
left 0
top 0
hexpand true
vexpand true
}
label("(1, 0)") {
left 1
top 0
}
label("(0, 1)") {
left 0
top 1
}
label("(1, 1)") {
left 1
top 1
}
}
}
tab_item('Align') {
grid {
label("(0, 0) halign/valign fill\nall available horizontal space is taken\nand\nall\navailable\nvertical\nspace\nis\ntaken") {
left 0
top 0
hexpand true unless OS.mac? # on Mac, only the first label is given all space, so avoid expanding
vexpand true unless OS.mac? # on Mac, only the first label is given all space, so avoid expanding
halign :fill
valign :fill
}
label("(1, 0) halign/valign start") {
left 1
top 0
hexpand true unless OS.mac? # on Mac, only the first label is given all space, so avoid expanding
vexpand true unless OS.mac? # on Mac, only the first label is given all space, so avoid expanding
halign :start
valign :start
}
label("(0, 1) halign/valign center") {
left 0
top 1
hexpand true unless OS.mac? # on Mac, only the first label is given all space, so avoid expanding
vexpand true unless OS.mac? # on Mac, only the first label is given all space, so avoid expanding
halign :center
valign :center
}
label("(1, 1) halign/valign end") {
left 1
top 1
hexpand true unless OS.mac? # on Mac, only the first label is given all space, so avoid expanding
vexpand true unless OS.mac? # on Mac, only the first label is given all space, so avoid expanding
halign :end
valign :end
}
}
}
}
}.show
```
### Form
[examples/form.rb](examples/form.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/form.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/form'"
```
Mac
![glimmer-dsl-libui-mac-form.png](images/glimmer-dsl-libui-mac-form.png)
![glimmer-dsl-libui-mac-form-msg-box.png](images/glimmer-dsl-libui-mac-form-msg-box.png)
Linux
![glimmer-dsl-libui-linux-form.png](images/glimmer-dsl-libui-linux-form.png)
![glimmer-dsl-libui-linux-form-msg-box.png](images/glimmer-dsl-libui-linux-form-msg-box.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('Form') {
margined true
vertical_box {
form {
@first_name_entry = entry {
label 'First Name' # label property is available when control is nested under form
}
@last_name_entry = entry {
label 'Last Name' # label property is available when control is nested under form
}
}
button('Display Name') {
on_clicked do
msg_box('Name', "#{@first_name_entry.text} #{@last_name_entry.text}")
end
}
}
}.show
```
### Basic Table
[examples/basic_table.rb](examples/basic_table.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/basic_table.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/basic_table'"
```
Mac
![glimmer-dsl-libui-mac-basic-table.png](images/glimmer-dsl-libui-mac-basic-table.png)
Linux
![glimmer-dsl-libui-linux-basic-table.png](images/glimmer-dsl-libui-linux-basic-table.png)
[LibUI](https://github.com/kojix2/LibUI) Original Version:
```ruby
require 'libui'
UI = LibUI
UI.init
main_window = UI.new_window('Animal sounds', 300, 200, 1)
hbox = UI.new_horizontal_box
UI.window_set_child(main_window, hbox)
data = [
%w[cat meow],
%w[dog woof],
%w[checken cock-a-doodle-doo],
%w[horse neigh],
%w[cow moo]
]
# Protects BlockCaller objects from garbage collection.
@blockcaller = []
def rbcallback(*args, &block)
args << [0] if args.size == 1 # Argument types are ommited
blockcaller = Fiddle::Closure::BlockCaller.new(*args, &block)
@blockcaller << blockcaller
blockcaller
end
model_handler = UI::FFI::TableModelHandler.malloc
model_handler.NumColumns = rbcallback(4) { 2 }
model_handler.ColumnType = rbcallback(4) { 0 }
model_handler.NumRows = rbcallback(4) { 5 }
model_handler.CellValue = rbcallback(1, [1, 1, 4, 4]) do |_, _, row, column|
UI.new_table_value_string(data[row][column])
end
model_handler.SetCellValue = rbcallback(0, [0]) {}
model = UI.new_table_model(model_handler)
table_params = UI::FFI::TableParams.malloc
table_params.Model = model
table_params.RowBackgroundColorModelColumn = -1
table = UI.new_table(table_params)
UI.table_append_text_column(table, 'Animal', 0, -1)
UI.table_append_text_column(table, 'Description', 1, -1)
UI.box_append(hbox, table, 1)
UI.control_show(main_window)
UI.window_on_closing(main_window) do
puts 'Bye Bye'
UI.control_destroy(main_window)
UI.free_table_model(model)
UI.quit
0
end
UI.main
UI.quit
```
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
data = [
%w[cat meow],
%w[dog woof],
%w[chicken cock-a-doodle-doo],
%w[horse neigh],
%w[cow moo]
]
window('Animal sounds', 300, 200) {
horizontal_box {
table {
text_column('Animal')
text_column('Description')
cell_rows data
}
}
on_closing do
puts 'Bye Bye'
end
}.show
```
### Editable Table
[examples/editable_table.rb](examples/editable_table.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/editable_table.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/editable_table'"
```
Mac
![glimmer-dsl-libui-mac-editable-table.png](images/glimmer-dsl-libui-mac-editable-table.png)
![glimmer-dsl-libui-mac-editable-table-editing.png](images/glimmer-dsl-libui-mac-editable-table-editing.png)
![glimmer-dsl-libui-mac-editable-table-edited.png](images/glimmer-dsl-libui-mac-editable-table-edited.png)
Linux
![glimmer-dsl-libui-linux-editable-table.png](images/glimmer-dsl-libui-linux-editable-table.png)
![glimmer-dsl-libui-linux-editable-table-editing.png](images/glimmer-dsl-libui-linux-editable-table-editing.png)
![glimmer-dsl-libui-linux-editable-table-edited.png](images/glimmer-dsl-libui-linux-editable-table-edited.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
data = [
%w[cat meow],
%w[dog woof],
%w[chicken cock-a-doodle-doo],
%w[horse neigh],
%w[cow moo]
]
window('Editable animal sounds', 300, 200) {
horizontal_box {
table {
text_column('Animal')
text_column('Description')
cell_rows data
editable true
}
}
on_closing do
puts 'Bye Bye'
end
}.show
```
### Editable Column Table
[examples/editable_column_table.rb](examples/editable_column_table.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/editable_column_table.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/editable_column_table'"
```
Mac
![glimmer-dsl-libui-mac-editable-column-table-editing.png](images/glimmer-dsl-libui-mac-editable-column-table-editing.png)
![glimmer-dsl-libui-mac-editable-column-table-edited.png](images/glimmer-dsl-libui-mac-editable-column-table-edited.png)
Linux
![glimmer-dsl-libui-linux-editable-column-table-editing.png](images/glimmer-dsl-libui-linux-editable-column-table-editing.png)
![glimmer-dsl-libui-linux-editable-column-table-edited.png](images/glimmer-dsl-libui-linux-editable-column-table-edited.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
data = [
%w[cat calm meow],
%w[dog loyal woof],
%w[chicken bird cock-a-doodle-doo],
%w[horse fast neigh],
%w[cow slow moo]
]
window('Editable column animal sounds', 400, 200) {
horizontal_box {
table {
text_column('Animal')
text_column('Description')
text_column('Sound (Editable)') {
editable true
}
cell_rows data
}
}
on_closing do
puts 'Bye Bye'
end
}.show
```
### Basic Table Image
This example has a prerequisite of installing `chunky_png` Ruby gem:
```
gem install chunky_png -v1.4.0
```
Also, note that behavior varies per platform (i.e. how `table` chooses to size images by default).
[examples/basic_table_image.rb](examples/basic_table_image.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/basic_table_image.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/basic_table_image'"
```
Mac
![glimmer-dsl-libui-mac-basic-table-image.png](images/glimmer-dsl-libui-mac-basic-table-image.png)
Linux
![glimmer-dsl-libui-linux-basic-table-image.png](images/glimmer-dsl-libui-linux-basic-table-image.png)
[LibUI](https://github.com/kojix2/LibUI) Original Version:
```ruby
# NOTE:
# This example displays images that can be freely downloaded from the Studio Ghibli website.
require 'libui'
require 'chunky_png'
require 'open-uri'
UI = LibUI
UI.init
main_window = UI.new_window('The Red Turtle', 310, 350, 0)
hbox = UI.new_horizontal_box
UI.window_set_child(main_window, hbox)
IMAGES = []
50.times do |i|
url = format('https://www.ghibli.jp/gallery/thumb-redturtle%03d.png', (i + 1))
puts "Processing Image: #{url}"
f = URI.open(url)
canvas = ChunkyPNG::Canvas.from_io(f)
f.close
data = canvas.to_rgba_stream
width = canvas.width
height = canvas.height
image = UI.new_image(width, height)
UI.image_append(image, data, width, height, width * 4)
IMAGES << image
rescue StandardError => e
warn url, e.message
end
# Protects BlockCaller objects from garbage collection.
@blockcaller = []
def rbcallback(*args, &block)
args << [0] if args.size == 1 # Argument types are ommited
blockcaller = Fiddle::Closure::BlockCaller.new(*args, &block)
@blockcaller << blockcaller
blockcaller
end
model_handler = UI::FFI::TableModelHandler.malloc
model_handler.NumColumns = rbcallback(4) { 1 }
model_handler.ColumnType = rbcallback(4) { 1 } # Image
model_handler.NumRows = rbcallback(4) { IMAGES.size }
model_handler.CellValue = rbcallback(1, [1, 1, 4, 4]) do |_, _, row, _column|
UI.new_table_value_image(IMAGES[row])
end
model_handler.SetCellValue = rbcallback(0, [0]) {}
model = UI.new_table_model(model_handler)
table_params = UI::FFI::TableParams.malloc
table_params.Model = model
table_params.RowBackgroundColorModelColumn = -1
table = UI.new_table(table_params)
UI.table_append_image_column(table, 'www.ghibli.jp/works/red-turtle', 0)
UI.box_append(hbox, table, 1)
UI.control_show(main_window)
UI.window_on_closing(main_window) do
puts 'Bye Bye'
UI.control_destroy(main_window)
UI.free_table_model(model)
IMAGES.each { |i| UI.free_image(i) }
UI.quit
0
end
UI.main
UI.quit
```
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
# NOTE:
# This example displays images that can be freely downloaded from the Studio Ghibli website.
require 'glimmer-dsl-libui'
require 'chunky_png'
require 'open-uri'
include Glimmer
IMAGE_ROWS = []
50.times do |i|
url = format('https://www.ghibli.jp/gallery/thumb-redturtle%03d.png', (i + 1))
puts "Processing Image: #{url}"
f = URI.open(url)
canvas = ChunkyPNG::Canvas.from_io(f)
f.close
data = canvas.to_rgba_stream
width = canvas.width
height = canvas.height
img = image {
image_part(data, width, height, width * 4)
}
IMAGE_ROWS << [img] # array of one column cell
rescue StandardError => e
warn url, e.message
end
window('The Red Turtle', 310, 350, false) {
horizontal_box {
table {
image_column('www.ghibli.jp/works/red-turtle', 0)
cell_rows IMAGE_ROWS
}
}
on_closing do
puts 'Bye Bye'
end
}.show
```
### Basic Table Image Text
This example has a prerequisite of installing `chunky_png` Ruby gem:
```
gem install chunky_png -v1.4.0
```
Also, note that behavior varies per platform (i.e. how `table` chooses to size images by default).
[examples/basic_table_image_text.rb](examples/basic_table_image_text.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/basic_table_image_text.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/basic_table_image_text'"
```
Mac
![glimmer-dsl-libui-mac-basic-table-image-text.png](images/glimmer-dsl-libui-mac-basic-table-image-text.png)
Linux
![glimmer-dsl-libui-linux-basic-table-image-text.png](images/glimmer-dsl-libui-linux-basic-table-image-text.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
# NOTE:
# This example displays images that can be freely downloaded from the Studio Ghibli website.
require 'glimmer-dsl-libui'
require 'chunky_png'
require 'open-uri'
include Glimmer
IMAGE_ROWS = []
5.times do |i|
url = format('https://www.ghibli.jp/gallery/thumb-redturtle%03d.png', (i + 1))
puts "Processing Image: #{url}"
f = URI.open(url)
canvas = ChunkyPNG::Canvas.from_io(f)
f.close
data = canvas.to_rgba_stream
width = canvas.width
height = canvas.height
img = image {
image_part(data, width, height, width * 4)
}
text = url.sub('https://www.ghibli.jp/gallery/thumb-redturtle', '').sub('.png', '')
IMAGE_ROWS << [[img, text], [img, text]] # cell values are dual-element arrays
rescue StandardError => e
warn url, e.message
end
window('The Red Turtle', 670, 350) {
horizontal_box {
table {
image_text_column('image/number')
image_text_column('image/number (editable)') {
editable true
}
cell_rows IMAGE_ROWS
}
}
}.show
```
### Basic Table Button
[examples/basic_table_button.rb](examples/basic_table_button.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/basic_table_button.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/basic_table_button'"
```
Mac
![glimmer-dsl-libui-mac-basic-table-button.png](images/glimmer-dsl-libui-mac-basic-table-button.png)
![glimmer-dsl-libui-mac-basic-table-button-deleted.png](images/glimmer-dsl-libui-mac-basic-table-button-deleted.png)
Linux
![glimmer-dsl-libui-linux-basic-table-button.png](images/glimmer-dsl-libui-linux-basic-table-button.png)
![glimmer-dsl-libui-linux-basic-table-button-deleted.png](images/glimmer-dsl-libui-linux-basic-table-button-deleted.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
data = [
%w[cat meow delete],
%w[dog woof delete],
%w[chicken cock-a-doodle-doo delete],
%w[horse neigh delete],
%w[cow moo delete]
]
window('Animal sounds', 300, 200) {
horizontal_box {
table {
text_column('Animal')
text_column('Description')
button_column('Action') {
on_clicked do |row|
data.delete_at(row) # automatically deletes actual table row due to implicit data-binding
end
}
cell_rows data # implicit data-binding
}
}
}.show
```
### Basic Table Checkbox
[examples/basic_table_checkbox.rb](examples/basic_table_checkbox.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/basic_table_checkbox.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/basic_table_checkbox'"
```
Mac
![glimmer-dsl-libui-mac-basic-table-checkbox.png](images/glimmer-dsl-libui-mac-basic-table-checkbox.png)
Linux
![glimmer-dsl-libui-linux-basic-table-checkbox.png](images/glimmer-dsl-libui-linux-basic-table-checkbox.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
data = [
['cat', 'meow', true],
['dog', 'woof', true],
['chicken', 'cock-a-doodle-doo', false],
['horse', 'neigh', true],
['cow', 'moo', true]
]
window('Animal sounds', 300, 200) {
horizontal_box {
table {
text_column('Animal')
text_column('Description')
checkbox_column('Mammal')
cell_rows data
}
}
}.show
```
### Basic Table Checkbox Text
[examples/basic_table_checkbox_text.rb](examples/basic_table_checkbox_text.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/basic_table_checkbox_text.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/basic_table_checkbox_text'"
```
Mac
![glimmer-dsl-libui-mac-basic-table-checkbox-text.png](images/glimmer-dsl-libui-mac-basic-table-checkbox-text.png)
Linux
![glimmer-dsl-libui-linux-basic-table-checkbox-text.png](images/glimmer-dsl-libui-linux-basic-table-checkbox-text.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
data = [
['cat', 'meow', [true, 'mammal']],
['dog', 'woof', [true, 'mammal']],
['chicken', 'cock-a-doodle-doo', [false, 'mammal']],
['horse', 'neigh', [true, 'mammal']],
['cow', 'moo', [true, 'mammal']]
]
window('Animal sounds', 400, 200) {
horizontal_box {
table {
text_column('Animal')
text_column('Sound')
checkbox_text_column('Description')
cell_rows data
}
}
}.show
```
### Basic Table Progress Bar
[examples/basic_table_progress_bar.rb](examples/basic_table_progress_bar.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/basic_table_progress_bar.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/basic_table_progress_bar'"
```
Mac
![glimmer-dsl-libui-mac-basic-table-progress-bar.png](images/glimmer-dsl-libui-mac-basic-table-progress-bar.png)
Linux
![glimmer-dsl-libui-linux-basic-table-progress-bar.png](images/glimmer-dsl-libui-linux-basic-table-progress-bar.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
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] = [row_data[0], 100] # automatically updates table due to implicit data-binding
end
end
}
}
}.show
```
### Form Table
[examples/form_table.rb](examples/form_table.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/form_table.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/form_table'"
```
Mac
![glimmer-dsl-libui-mac-form-table.png](images/glimmer-dsl-libui-mac-form-table.png)
![glimmer-dsl-libui-mac-form-table-contact-entered.png](images/glimmer-dsl-libui-mac-form-table-contact-entered.png)
![glimmer-dsl-libui-mac-form-table-filtered.png](images/glimmer-dsl-libui-mac-form-table-filtered.png)
Linux
![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png)
![glimmer-dsl-libui-linux-form-table-contact-entered.png](images/glimmer-dsl-libui-linux-form-table-contact-entered.png)
![glimmer-dsl-libui-linux-form-table-filtered.png](images/glimmer-dsl-libui-linux-form-table-filtered.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
# frozen_string_literal: true
require 'glimmer-dsl-libui'
include Glimmer
data = [
['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
]
window('Contacts', 600, 600) { |w|
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.include?('')
msg_box_error(w, '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')
cell_rows data # implicit data-binding
}
}
}.show
```
### Basic Area
[examples/basic_area.rb](examples/basic_area.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/basic_area.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/basic_area'"
```
Mac
![glimmer-dsl-libui-mac-basic-area.png](images/glimmer-dsl-libui-mac-basic-area.png)
Linux
![glimmer-dsl-libui-linux-basic-area.png](images/glimmer-dsl-libui-linux-basic-area.png)
[LibUI](https://github.com/kojix2/LibUI) Original Version:
```ruby
require 'libui'
UI = LibUI
UI.init
handler = UI::FFI::AreaHandler.malloc
area = UI.new_area(handler)
brush = UI::FFI::DrawBrush.malloc
handler_draw_event = Fiddle::Closure::BlockCaller.new(0, [1, 1, 1]) do |_, _, area_draw_params|
path = UI.draw_new_path(0)
UI.draw_path_add_rectangle(path, 0, 0, 400, 400)
UI.draw_path_end(path)
brush.Type = 0
brush.R = 0.4
brush.G = 0.4
brush.B = 0.8
brush.A = 1.0
area_draw_params = UI::FFI::AreaDrawParams.new(area_draw_params)
UI.draw_fill(area_draw_params.Context, path, brush.to_ptr)
UI.draw_free_path(path)
end
handler.Draw = handler_draw_event
handler.MouseEvent = Fiddle::Closure::BlockCaller.new(0, [0]) {}
handler.MouseCrossed = Fiddle::Closure::BlockCaller.new(0, [0]) {}
handler.DragBroken = Fiddle::Closure::BlockCaller.new(0, [0]) {}
handler.KeyEvent = Fiddle::Closure::BlockCaller.new(0, [0]) {}
box = UI.new_vertical_box
UI.box_set_padded(box, 1)
UI.box_append(box, area, 1)
main_window = UI.new_window('Basic Area', 400, 400, 1)
UI.window_set_margined(main_window, 1)
UI.window_set_child(main_window, box)
UI.window_on_closing(main_window) do
UI.control_destroy(main_window)
UI.quit
0
end
UI.control_show(main_window)
UI.main
UI.quit
```
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
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
```
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (semi-declarative `on_draw` dynamic `path` approach):
```ruby
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
```
### Dynamic Area
[examples/dynamic_area.rb](examples/dynamic_area.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/dynamic_area.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/dynamic_area'"
```
Mac
![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-dynamic-area.png)
![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-dynamic-area-updated.png)
Linux
![glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-dynamic-area.png)
![glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-dynamic-area-updated.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('Dynamic Area', 240, 500) {
margined true
vertical_box {
label('Rectangle Properties') {
stretchy false
}
form {
stretchy false
@x_spinbox = spinbox(0, 1000) {
label 'x'
value 25
on_changed do
@area.queue_redraw_all
end
}
@y_spinbox = spinbox(0, 1000) {
label 'y'
value 25
on_changed do
@area.queue_redraw_all
end
}
@width_spinbox = spinbox(0, 1000) {
label 'width'
value 150
on_changed do
@area.queue_redraw_all
end
}
@height_spinbox = spinbox(0, 1000) {
label 'height'
value 150
on_changed do
@area.queue_redraw_all
end
}
@red_spinbox = spinbox(0, 255) {
label 'red'
value 102
on_changed do
@area.queue_redraw_all
end
}
@green_spinbox = spinbox(0, 255) {
label 'green'
value 102
on_changed do
@area.queue_redraw_all
end
}
@blue_spinbox = spinbox(0, 255) {
label 'blue'
value 204
on_changed do
@area.queue_redraw_all
end
}
@alpha_spinbox = spinbox(0, 100) {
label 'alpha'
value 100
on_changed do
@area.queue_redraw_all
end
}
}
@area = area {
on_draw do |area_draw_params|
path { # a dynamic path is added semi-declaratively inside on_draw block
rectangle(@x_spinbox.value, @y_spinbox.value, @width_spinbox.value, @height_spinbox.value)
fill r: @red_spinbox.value, g: @green_spinbox.value, b: @blue_spinbox.value, a: @alpha_spinbox.value / 100.0
}
end
}
}
}.show
```
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (declarative stable `path` approach):
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('Dynamic Area', 240, 600) {
margined true
vertical_box {
label('Rectangle Properties') {
stretchy false
}
form {
stretchy false
@x_spinbox = spinbox(0, 1000) {
label 'x'
value 25
on_changed do
@rectangle.x = @x_spinbox.value # updating properties automatically triggers area.queue_redraw_all
end
}
@y_spinbox = spinbox(0, 1000) {
label 'y'
value 25
on_changed do
@rectangle.y = @y_spinbox.value # updating properties automatically triggers area.queue_redraw_all
end
}
@width_spinbox = spinbox(0, 1000) {
label 'width'
value 150
on_changed do
@rectangle.width = @width_spinbox.value # updating properties automatically triggers area.queue_redraw_all
end
}
@height_spinbox = spinbox(0, 1000) {
label 'height'
value 150
on_changed do
@rectangle.height = @height_spinbox.value # updating properties automatically triggers area.queue_redraw_all
end
}
@red_spinbox = spinbox(0, 255) {
label 'red'
value 102
on_changed do
@path.fill[:r] = @red_spinbox.value # updating hash properties automatically triggers area.queue_redraw_all
end
}
@green_spinbox = spinbox(0, 255) {
label 'green'
value 102
on_changed do
@path.fill[:g] = @green_spinbox.value # updating hash properties automatically triggers area.queue_redraw_all
end
}
@blue_spinbox = spinbox(0, 255) {
label 'blue'
value 204
on_changed do
@path.fill[:b] = @blue_spinbox.value # updating hash properties automatically triggers area.queue_redraw_all
end
}
@alpha_spinbox = spinbox(0, 100) {
label 'alpha'
value 100
on_changed do
@path.fill[:a] = @alpha_spinbox.value / 100.0 # updating hash properties automatically triggers area.queue_redraw_all
end
}
}
area {
@path = path { # stable path
@rectangle = rectangle(@x_spinbox.value, @y_spinbox.value, @width_spinbox.value, @height_spinbox.value)
fill r: @red_spinbox.value, g: @green_spinbox.value, b: @blue_spinbox.value, a: @alpha_spinbox.value / 100.0
}
}
}
}.show
```
### Area Gallery
[examples/area_gallery.rb](examples/area_gallery.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/area_gallery.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/area_gallery'"
```
Mac
![glimmer-dsl-libui-mac-area-gallery.png](images/glimmer-dsl-libui-mac-area-gallery.png)
Linux
![glimmer-dsl-libui-linux-area-gallery.png](images/glimmer-dsl-libui-linux-area-gallery.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('Area Gallery', 400, 400) {
area {
path { # declarative stable path
square(0, 0, 100)
square(100, 100, 400)
fill r: 102, g: 102, b: 204
}
path { # declarative stable path
rectangle(0, 100, 100, 400)
rectangle(100, 0, 400, 100)
fill r: 204, g: 102, b: 204
}
path { # declarative stable path
figure(100, 100) {
line(100, 400)
line(400, 100)
line(400, 400)
closed true
}
fill r: 202, g: 102, b: 104, a: 0.5
stroke r: 0, g: 0, b: 0
}
path { # declarative stable path
figure(0, 0) {
bezier(200, 100, 100, 200, 400, 100)
bezier(300, 100, 100, 300, 100, 400)
bezier(100, 300, 300, 100, 400, 400)
closed true
}
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
}
path { # declarative stable path
circle(200, 200, 90)
fill r: 202, g: 102, b: 204, a: 0.5
stroke r: 0, g: 0, b: 0, thickness: 2
}
path { # declarative stable path
arc(400, 220, 180, 90, 90, false)
fill r: 204, 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
}
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
```
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (setting shape properties instead of arguments):
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('Area Gallery', 400, 400) {
area {
path { # declarative stable path
square {
x 0
y 0
length 100
}
square {
x 100
y 100
length 400
}
fill r: 102, g: 102, b: 204
}
path { # declarative stable path
rectangle {
x 0
y 100
width 100
height 400
}
rectangle {
x 100
y 0
width 400
height 100
}
fill r: 204, g: 102, b: 204
}
path { # declarative stable path
figure {
x 100
y 100
line {
x 100
y 400
}
line {
x 400
y 100
}
line {
x 400
y 400
}
closed true
}
fill r: 202, g: 102, b: 104, a: 0.5
stroke r: 0, g: 0, b: 0
}
path { # declarative stable path
figure {
x 0
y 0
bezier {
c1_x 200
c1_y 100
c2_x 100
c2_y 200
end_x 400
end_y 100
}
bezier {
c1_x 300
c1_y 100
c2_x 100
c2_y 300
end_x 100
end_y 400
}
bezier {
c1_x 100
c1_y 300
c2_x 300
c2_y 100
end_x 400
end_y 400
}
closed true
}
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
}
path { # declarative stable path
circle {
x_center 200
y_center 200
radius 90
}
fill r: 202, g: 102, b: 204, a: 0.5
stroke r: 0, g: 0, b: 0, thickness: 2
}
path { # declarative stable path
arc {
x_center 400
y_center 220
radius 180
start_angle 90
sweep 90
is_negative false
}
fill r: 204, 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
}
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
```
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (semi-declarative `on_draw` dynamic `path` approach):
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('Area Gallery', 400, 400) {
area {
on_draw do |area_draw_params|
path { # a dynamic path is added semi-declaratively inside on_draw block
square(0, 0, 100)
square(100, 100, 400)
fill r: 102, g: 102, b: 204
}
path { # a dynamic path is added semi-declaratively inside on_draw block
rectangle(0, 100, 100, 400)
rectangle(100, 0, 400, 100)
fill r: 204, g: 102, b: 204
}
path { # a dynamic path is added semi-declaratively inside on_draw block
figure(100, 100) {
line(100, 400)
line(400, 100)
line(400, 400)
closed true
}
fill r: 202, g: 102, b: 104, a: 0.5
stroke r: 0, g: 0, b: 0
}
path { # a dynamic path is added semi-declaratively inside on_draw block
figure(0, 0) {
bezier(200, 100, 100, 200, 400, 100)
bezier(300, 100, 100, 300, 100, 400)
bezier(100, 300, 300, 100, 400, 400)
closed true
}
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
}
path { # a dynamic path is added semi-declaratively inside on_draw block
circle(200, 200, 90)
fill r: 202, g: 102, b: 204, a: 0.5
stroke r: 0, g: 0, b: 0, thickness: 2
}
path { # a dynamic path is added semi-declaratively inside on_draw block
arc(400, 220, 180, 90, 90, false)
fill r: 204, 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
}
end
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
```
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (setting shape properties instead of arguments with semi-declarative `on_draw` dynamic `path` approach):
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('Area Gallery', 400, 400) {
area {
on_draw do |area_draw_params|
path { # a dynamic path is added semi-declaratively inside on_draw block
square {
x 0
y 0
length 100
}
square {
x 100
y 100
length 400
}
fill r: 102, g: 102, b: 204
}
path { # a dynamic path is added semi-declaratively inside on_draw block
rectangle {
x 0
y 100
width 100
height 400
}
rectangle {
x 100
y 0
width 400
height 100
}
fill r: 204, g: 102, b: 204
}
path { # a dynamic path is added semi-declaratively inside on_draw block
figure {
x 100
y 100
line {
x 100
y 400
}
line {
x 400
y 100
}
line {
x 400
y 400
}
closed true
}
fill r: 202, g: 102, b: 104, a: 0.5
stroke r: 0, g: 0, b: 0
}
path { # a dynamic path is added semi-declaratively inside on_draw block
figure {
x 0
y 0
bezier {
c1_x 200
c1_y 100
c2_x 100
c2_y 200
end_x 400
end_y 100
}
bezier {
c1_x 300
c1_y 100
c2_x 100
c2_y 300
end_x 100
end_y 400
}
bezier {
c1_x 100
c1_y 300
c2_x 300
c2_y 100
end_x 400
end_y 400
}
closed true
}
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
}
path { # a dynamic path is added semi-declaratively inside on_draw block
circle {
x_center 200
y_center 200
radius 90
}
fill r: 202, g: 102, b: 204, a: 0.5
stroke r: 0, g: 0, b: 0, thickness: 2
}
path { # a dynamic path is added semi-declaratively inside on_draw block
arc {
x_center 400
y_center 220
radius 180
start_angle 90
sweep 90
is_negative false
}
fill r: 204, 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
}
end
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
```
### Histogram
[examples/histogram.rb](examples/histogram.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/histogram.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/histogram'"
```
Mac
![glimmer-dsl-libui-mac-histogram.png](images/glimmer-dsl-libui-mac-histogram.png)
Linux
![glimmer-dsl-libui-linux-histogram.png](images/glimmer-dsl-libui-linux-histogram.png)
[LibUI](https://github.com/kojix2/LibUI) Original Version:
```ruby
# https://github.com/jamescook/libui-ruby/blob/master/example/histogram.rb
require 'libui'
UI = LibUI
X_OFF_LEFT = 20
Y_OFF_TOP = 20
X_OFF_RIGHT = 20
Y_OFF_BOTTOM = 20
POINT_RADIUS = 5
init = UI.init
handler = UI::FFI::AreaHandler.malloc
histogram = UI.new_area(handler)
brush = UI::FFI::DrawBrush.malloc
color_button = UI.new_color_button
blue = 0x1E90FF
datapoints = []
def graph_size(area_width, area_height)
graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
graph_height = area_height - Y_OFF_TOP - Y_OFF_BOTTOM
[graph_width, graph_height]
end
matrix = UI::FFI::DrawMatrix.malloc
def point_locations(datapoints, width, height)
xincr = width / 9.0 # 10 - 1 to make the last point be at the end
yincr = height / 100.0
data = []
datapoints.each_with_index do |dp, i|
val = 100 - UI.spinbox_value(dp)
data << [xincr * i, yincr * val]
i += 1
end
data
end
def construct_graph(datapoints, width, height, should_extend)
locations = point_locations(datapoints, width, height)
path = UI.draw_new_path(0) # winding
first_location = locations[0] # x and y
UI.draw_path_new_figure(path, first_location[0], first_location[1])
locations.each do |loc|
UI.draw_path_line_to(path, loc[0], loc[1])
end
if should_extend
UI.draw_path_line_to(path, width, height)
UI.draw_path_line_to(path, 0, height)
UI.draw_path_close_figure(path)
end
UI.draw_path_end(path)
path
end
handler_draw_event = Fiddle::Closure::BlockCaller.new(
0, [1, 1, 1]
) do |_area_handler, _area, area_draw_params|
area_draw_params = UI::FFI::AreaDrawParams.new(area_draw_params)
path = UI.draw_new_path(0) # winding
UI.draw_path_add_rectangle(path, 0, 0, area_draw_params.AreaWidth, area_draw_params.AreaHeight)
UI.draw_path_end(path)
set_solid_brush(brush, 0xFFFFFF, 1.0) # white
UI.draw_fill(area_draw_params.Context, path, brush.to_ptr)
UI.draw_free_path(path)
dsp = UI::FFI::DrawStrokeParams.malloc
dsp.Cap = 0 # flat
dsp.Join = 0 # miter
dsp.Thickness = 2
dsp.MiterLimit = 10 # DEFAULT_MITER_LIMIT
dashes = Fiddle::Pointer.malloc(8)
dsp.Dashes = dashes
dsp.NumDashes = 0
dsp.DashPhase = 0
# draw axes
set_solid_brush(brush, 0x000000, 1.0) # black
graph_width, graph_height = *graph_size(area_draw_params.AreaWidth, area_draw_params.AreaHeight)
path = UI.draw_new_path(0) # winding
UI.draw_path_new_figure(path, X_OFF_LEFT, Y_OFF_TOP)
UI.draw_path_line_to(path, X_OFF_LEFT, Y_OFF_TOP + graph_height)
UI.draw_path_line_to(path, X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
UI.draw_path_end(path)
UI.draw_stroke(area_draw_params.Context, path, brush, dsp)
UI.draw_free_path(path)
# now transform the coordinate space so (0, 0) is the top-left corner of the graph
UI.draw_matrix_set_identity(matrix)
UI.draw_matrix_translate(matrix, X_OFF_LEFT, Y_OFF_TOP)
UI.draw_transform(area_draw_params.Context, matrix)
# now get the color for the graph itself and set up the brush
# uiColorButtonColor(colorButton, &graphR, &graphG, &graphB, &graphA)
graph_r = Fiddle::Pointer.malloc(8) # double
graph_g = Fiddle::Pointer.malloc(8) # double
graph_b = Fiddle::Pointer.malloc(8) # double
graph_a = Fiddle::Pointer.malloc(8) # double
UI.color_button_color(color_button, graph_r, graph_g, graph_b, graph_a)
brush.Type = 0 # solid
brush.R = graph_r[0, 8].unpack1('d')
brush.G = graph_g[0, 8].unpack1('d')
brush.B = graph_b[0, 8].unpack1('d')
# now create the fill for the graph below the graph line
path = construct_graph(datapoints, graph_width, graph_height, true)
brush.A = graph_a[0, 8].unpack1('d') / 2.0
UI.draw_fill(area_draw_params.Context, path, brush)
UI.draw_free_path(path)
# now draw the histogram line
path = construct_graph(datapoints, graph_width, graph_height, false)
brush.A = graph_a[0, 8].unpack1('d')
UI.draw_stroke(area_draw_params.Context, path, brush, dsp)
UI.draw_free_path(path)
end
handler.Draw = handler_draw_event
# Assigning to local variables
# This is intended to protect Fiddle::Closure from garbage collection.
# See https://github.com/kojix2/LibUI/issues/8
handler.MouseEvent = (c1 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
handler.MouseCrossed = (c2 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
handler.DragBroken = (c3 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
handler.KeyEvent = (c4 = Fiddle::Closure::BlockCaller.new(1, [0]) { 0 })
UI.freeInitError(init) unless init.nil?
hbox = UI.new_horizontal_box
UI.box_set_padded(hbox, 1)
vbox = UI.new_vertical_box
UI.box_set_padded(vbox, 1)
UI.box_append(hbox, vbox, 0)
UI.box_append(hbox, histogram, 1)
datapoints = Array.new(10) do
UI.new_spinbox(0, 100).tap do |datapoint|
UI.spinbox_set_value(datapoint, Random.new.rand(90))
UI.spinbox_on_changed(datapoint) do
UI.area_queue_redraw_all(histogram)
end
UI.box_append(vbox, datapoint, 0)
end
end
def set_solid_brush(brush, color, alpha)
brush.Type = 0 # solid
brush.R = ((color >> 16) & 0xFF) / 255.0
brush.G = ((color >> 8) & 0xFF) / 255.0
brush.B = (color & 0xFF) / 255.0
brush.A = alpha
brush
end
set_solid_brush(brush, blue, 1.0)
UI.color_button_set_color(color_button, brush.R, brush.G, brush.B, brush.A)
UI.color_button_on_changed(color_button) do
UI.area_queue_redraw_all(histogram)
end
UI.box_append(vbox, color_button, 0)
MAIN_WINDOW = UI.new_window('histogram example', 640, 480, 1)
UI.window_set_margined(MAIN_WINDOW, 1)
UI.window_set_child(MAIN_WINDOW, hbox)
should_quit = proc do |_ptr|
UI.control_destroy(MAIN_WINDOW)
UI.quit
0
end
UI.window_on_closing(MAIN_WINDOW, should_quit)
UI.on_should_quit(should_quit)
UI.control_show(MAIN_WINDOW)
UI.main
UI.quit
```
[Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
# https://github.com/jamescook/libui-ruby/blob/master/example/histogram.rb
require 'glimmer-dsl-libui'
include Glimmer
X_OFF_LEFT = 20
Y_OFF_TOP = 20
X_OFF_RIGHT = 20
Y_OFF_BOTTOM = 20
POINT_RADIUS = 5
COLOR_BLUE = 0x1E90FF
@datapoints = 10.times.map {Random.new.rand(90)}
def graph_size(area_width, area_height)
graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
graph_height = area_height - Y_OFF_TOP - Y_OFF_BOTTOM
[graph_width, graph_height]
end
def point_locations(width, height)
xincr = width / 9.0 # 10 - 1 to make the last point be at the end
yincr = height / 100.0
@datapoints.each_with_index.map do |value, i|
val = 100 - value
[xincr * i, yincr * val]
end
end
# method-based custom control representing a graph path
def graph_path(width, height, should_extend, &block)
locations = point_locations(width, height)
path {
first_location = locations[0] # x and y
figure(first_location[0], first_location[1]) {
locations.each do |loc|
line(loc[0], loc[1])
end
if should_extend
line(width, height)
line(0, height)
closed true
end
}
# apply a transform to the coordinate space for this path so (0, 0) is the top-left corner of the graph
transform {
translate X_OFF_LEFT, Y_OFF_TOP
}
block.call
}
end
window('histogram example', 640, 480) {
margined true
horizontal_box {
vertical_box {
stretchy false
10.times do |i|
spinbox(0, 100) { |sb|
stretchy false
value @datapoints[i]
on_changed do
@datapoints[i] = sb.value
@area.queue_redraw_all
end
}
end
@color_button = color_button {
stretchy false
color COLOR_BLUE
on_changed do
@area.queue_redraw_all
end
}
}
@area = area {
on_draw do |area_draw_params|
path {
rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height])
fill 0xFFFFFF
}
graph_width, graph_height = *graph_size(area_draw_params[:area_width], area_draw_params[:area_height])
path {
figure(X_OFF_LEFT, Y_OFF_TOP) {
line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
}
stroke 0x000000, thickness: 2, miter_limit: 10
}
# now create the fill for the graph below the graph line
graph_path(graph_width, graph_height, true) {
fill @color_button.color.merge(a: 0.5)
}
# now draw the histogram line
graph_path(graph_width, graph_height, false) {
stroke @color_button.color.merge(thickness: 2, miter_limit: 10)
}
end
}
}
}.show
```
### Basic Transform
[examples/basic_transform.rb](examples/basic_transform.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/basic_transform.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/basic_transform'"
```
Mac
![glimmer-dsl-libui-mac-basic-transform.png](images/glimmer-dsl-libui-mac-basic-transform.png)
Linux
![glimmer-dsl-libui-linux-basic-transform.png](images/glimmer-dsl-libui-linux-basic-transform.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
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
```
### Login
[examples/login.rb](examples/login.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/login.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/login'"
```
Mac
![glimmer-dsl-libui-mac-login.png](images/glimmer-dsl-libui-mac-login.png)
![glimmer-dsl-libui-mac-login-logged-in.png](images/glimmer-dsl-libui-mac-login-logged-in.png)
Linux
![glimmer-dsl-libui-linux-login.png](images/glimmer-dsl-libui-linux-login.png)
![glimmer-dsl-libui-linux-login-logged-in.png](images/glimmer-dsl-libui-linux-login-logged-in.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
include Glimmer
window('Login') {
margined true
vertical_box {
form {
@username_entry = entry {
label 'Username:'
}
@password_entry = password_entry {
label 'Password:'
}
}
horizontal_box {
@login_button = button('Login') {
on_clicked do
@username_entry.enabled = false
@password_entry.enabled = false
@login_button.enabled = false
@logout_button.enabled = true
end
}
@logout_button = button('Logout') {
enabled false
on_clicked do
@username_entry.text = ''
@password_entry.text = ''
@username_entry.enabled = true
@password_entry.enabled = true
@login_button.enabled = true
@logout_button.enabled = false
end
}
}
}
}.show
```
### Timer
[examples/timer.rb](examples/timer.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/timer.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/timer'"
```
Mac
![glimmer-dsl-libui-mac-timer.png](images/glimmer-dsl-libui-mac-timer.png)
![glimmer-dsl-libui-mac-timer-in-progress.png](images/glimmer-dsl-libui-mac-timer-in-progress.png)
Linux
![glimmer-dsl-libui-linux-timer.png](images/glimmer-dsl-libui-linux-timer.png)
![glimmer-dsl-libui-linux-timer-in-progress.png](images/glimmer-dsl-libui-linux-timer-in-progress.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
# frozen_string_literal: true
require 'glimmer-dsl-libui'
class Timer
include Glimmer
SECOND_MAX = 59
MINUTE_MAX = 59
HOUR_MAX = 23
def initialize
@pid = nil
@midi_file = File.expand_path('../sounds/AlanWalker-Faded.mid', __dir__)
at_exit { stop_midi }
setup_timer
create_gui
end
def stop_midi
if @pid
if @th.alive?
Process.kill(:SIGKILL, @pid)
@pid = nil
else
@pid = nil
end
end
end
def play_midi
stop_midi
if @pid.nil?
begin
@pid = spawn "timidity -G 0.0-10.0 #{@midi_file}"
@th = Process.detach @pid
rescue Errno::ENOENT
warn 'Timidty++ not found. Please install Timidity++.'
warn 'https://sourceforge.net/projects/timidity/'
end
end
end
def setup_timer
unless @setup_timer
Glimmer::LibUI.timer(1) do
if @started
seconds = @sec_spinbox.value
minutes = @min_spinbox.value
hours = @hour_spinbox.value
if seconds > 0
@sec_spinbox.value = seconds -= 1
end
if seconds == 0
if minutes > 0
@min_spinbox.value = minutes -= 1
@sec_spinbox.value = seconds = SECOND_MAX
end
if minutes == 0
if hours > 0
@hour_spinbox.value = hours -= 1
@min_spinbox.value = minutes = MINUTE_MAX
@sec_spinbox.value = seconds = SECOND_MAX
end
if hours == 0 && minutes == 0 && seconds == 0
@start_button.enabled = true
@stop_button.enabled = false
@started = false
unless @played
play_midi
@played = true
end
end
end
end
end
end
@setup_timer = true
end
end
def create_gui
window('Timer') {
margined true
group('Countdown') {
vertical_box {
horizontal_box {
@hour_spinbox = spinbox(0, HOUR_MAX) {
stretchy false
value 0
}
label(':') {
stretchy false
}
@min_spinbox = spinbox(0, MINUTE_MAX) {
stretchy false
value 0
}
label(':') {
stretchy false
}
@sec_spinbox = spinbox(0, SECOND_MAX) {
stretchy false
value 0
}
}
horizontal_box {
@start_button = button('Start') {
on_clicked do
@start_button.enabled = false
@stop_button.enabled = true
@started = true
@played = false
end
}
@stop_button = button('Stop') {
enabled false
on_clicked do
@start_button.enabled = true
@stop_button.enabled = false
@started = false
end
}
}
}
}
}.show
end
end
Timer.new
```
### Color The Circles
[examples/color_the_circles.rb](examples/color_the_circles.rb)
Run with this command from the root of the project if you cloned the project:
```
ruby -r './lib/glimmer-dsl-libui' examples/color_the_circles.rb
```
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
```
ruby -r glimmer-dsl-libui -e "require 'examples/color_the_circles'"
```
Mac
![glimmer-dsl-libui-mac-color-the-circles.png](images/glimmer-dsl-libui-mac-color-the-circles.png)
![glimmer-dsl-libui-mac-color-the-circles-lost.png](images/glimmer-dsl-libui-mac-color-the-circles-lost.png)
![glimmer-dsl-libui-mac-color-the-circles-won.png](images/glimmer-dsl-libui-mac-color-the-circles-won.png)
Linux
![glimmer-dsl-libui-linux-color-the-circles.png](images/glimmer-dsl-libui-linux-color-the-circles.png)
![glimmer-dsl-libui-linux-color-the-circles-lost.png](images/glimmer-dsl-libui-linux-color-the-circles-lost.png)
![glimmer-dsl-libui-linux-color-the-circles-won.png](images/glimmer-dsl-libui-linux-color-the-circles-won.png)
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
```ruby
require 'glimmer-dsl-libui'
class ColorTheCircles
include Glimmer
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
CIRCLE_MIN_RADIUS = 15
CIRCLE_MAX_RADIUS = 50
MARGIN_WIDTH = 55
MARGIN_HEIGHT = 155
TIME_MAX_EASY = 4
TIME_MAX_MEDIUM = 3
TIME_MAX_HARD = 2
TIME_MAX_INSANE = 1
attr_accessor :score
def initialize
@circles_data = []
@score = 0
@time_max = TIME_MAX_HARD
register_observers
setup_circle_factory
end
def register_observers
observer = Glimmer::DataBinding::Observer.proc do |new_score|
@score_label.text = new_score.to_s
if new_score == -20
msg_box('You Lost!', 'Sorry! Your score reached -20')
restart_game
elsif new_score == 0
msg_box('You Won!', 'Congratulations! Your score reached 0')
restart_game
end
end
observer.observe(self, :score) # automatically enhances self to become Glimmer::DataBinding::ObservableModel and notify observer on score attribute changes
end
def setup_circle_factory
consumer = Proc.new do
if @circles_data.empty?
# start with 3 circles to make more challenging
add_circle until @circles_data.size > 3
else
add_circle
end
delay = rand * @time_max
Glimmer::LibUI.timer(delay, repeat: false, &consumer)
end
Glimmer::LibUI.queue_main(&consumer)
end
def add_circle
circle_x_center = rand * (WINDOW_WIDTH - MARGIN_WIDTH - CIRCLE_MAX_RADIUS) + CIRCLE_MAX_RADIUS
circle_y_center = rand * (WINDOW_HEIGHT - MARGIN_HEIGHT - CIRCLE_MAX_RADIUS) + CIRCLE_MAX_RADIUS
circle_radius = rand * (CIRCLE_MAX_RADIUS - CIRCLE_MIN_RADIUS) + CIRCLE_MIN_RADIUS
stroke_color = Glimmer::LibUI.x11_colors.sample
@circles_data << {
args: [circle_x_center, circle_y_center, circle_radius],
fill: nil,
stroke: stroke_color
}
@area.queue_redraw_all
self.score -= 1 # notifies score observers automatically of change
end
def restart_game
@score = 0 # update variable directly to avoid notifying observers
@circles_data.clear
end
def launch
menu('Actions') {
menu_item('Restart') {
on_clicked do
restart_game
end
}
quit_menu_item
}
menu('Difficulty') {
radio_menu_item('Easy') {
on_clicked do
@time_max = TIME_MAX_EASY
end
}
radio_menu_item('Medium') {
on_clicked do
@time_max = TIME_MAX_MEDIUM
end
}
radio_menu_item('Hard') {
checked true
on_clicked do
@time_max = TIME_MAX_HARD
end
}
radio_menu_item('Insane') {
on_clicked do
@time_max = TIME_MAX_INSANE
end
}
}
menu('Help') {
menu_item('Instructions') {
on_clicked do
msg_box('Instructions', "Score goes down as circles are added.\nIf it reaches -20, you lose!\n\nClick circles to color and score!\nOnce score reaches 0, you win!\n\nBeware of concealed light-colored circles!\nThey are revealed once darker circles intersect them.\n\nThere are four levels of difficulty.\nChange via difficulty menu if the game gets too tough.")
end
}
}
window('Color The Circles', WINDOW_WIDTH, WINDOW_HEIGHT) {
margined true
grid {
button('Restart') {
left 0
top 0
halign :center
on_clicked do
restart_game
end
}
label('Score goes down as circles are added. If it reaches -20, you lose!') {
left 0
top 1
halign :center
}
label('Click circles to color and score! Once score reaches 0, you win!') {
left 0
top 2
halign :center
}
horizontal_box {
left 0
top 3
halign :center
label('Score:') {
stretchy false
}
@score_label = label(@score.to_s) {
stretchy false
}
}
vertical_box {
left 0
top 4
hexpand true
vexpand true
halign :fill
valign :fill
@area = area {
on_draw do |area_draw_params|
path {
rectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT)
fill :white
}
@circles_data.each do |circle_data|
path {
circle_data[:circle] = circle(*circle_data[:args])
fill circle_data[:fill]
stroke circle_data[:stroke]
}
end
end
on_mouse_down do |area_mouse_event|
clicked_circle_data = @circles_data.find do |circle_data|
circle_data[:fill].nil? && circle_data[:circle].include?(area_mouse_event[:x], area_mouse_event[:y])
end
if clicked_circle_data
clicked_circle_data[:fill] = clicked_circle_data[:stroke]
@area.queue_redraw_all
self.score += 1 # notifies score observers automatically of change
end
end
}
}
}
}.show
end
end
ColorTheCircles.new.launch
```
## Contributing to glimmer-dsl-libui
- 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.
## Help
### Issues
You may submit [issues](https://github.com/AndyObtiva/glimmer/issues) on [GitHub](https://github.com/AndyObtiva/glimmer/issues).
[Click here to submit an issue.](https://github.com/AndyObtiva/glimmer/issues)
### Chat
If you need live help, try to [![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Process
[Glimmer Process](https://github.com/AndyObtiva/glimmer/blob/master/PROCESS.md)
## 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](https://rubygems.org/gems/glimmer-dsl-libui). You are welcome to contribute more feature suggestions.
[TODO.md](TODO.md)
## Change Log
[CHANGELOG.md](CHANGELOG.md)
## Contributors
* [Andy Maleh](https://github.com/AndyObtiva) (Founder)
[Click here to view contributor commits.](https://github.com/AndyObtiva/glimmer-dsl-libui/graphs/contributors)
## License
[MIT](LICENSE.txt)
Copyright (c) 2021 Andy Maleh
--
[](https://github.com/AndyObtiva/glimmer) Built for [Glimmer](https://github.com/AndyObtiva/glimmer) (DSL Framework).