{"id":13878051,"url":"https://github.com/AndyObtiva/glimmer-dsl-libui","last_synced_at":"2025-07-16T14:30:46.111Z","repository":{"id":38616516,"uuid":"406501351","full_name":"AndyObtiva/glimmer-dsl-libui","owner":"AndyObtiva","description":"Glimmer DSL for LibUI - Prerequisite-Free Ruby Desktop Development Cross-Platform Native GUI Library - The Quickest Way From Zero To GUI - If You Liked Shoes, You'll Love Glimmer! - No need to pre-install any prerequisites. Just install the gem and have platform-independent GUI that just works on Mac, Windows, and Linux.","archived":false,"fork":false,"pushed_at":"2024-09-23T23:07:17.000Z","size":14389,"stargazers_count":499,"open_issues_count":20,"forks_count":15,"subscribers_count":17,"default_branch":"master","last_synced_at":"2024-10-12T04:05:31.651Z","etag":null,"topics":["desktop","dsl","dsl-syntax","framework","glimmer","glimmer-dsl","gui","libui","ruby","ruby-gem","ruby-library","rubygem"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AndyObtiva.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-09-14T19:46:10.000Z","updated_at":"2024-10-09T19:36:11.000Z","dependencies_parsed_at":"2024-02-27T00:24:36.017Z","dependency_job_id":"815fae1e-ed3f-40af-b7ed-51335a0c1576","html_url":"https://github.com/AndyObtiva/glimmer-dsl-libui","commit_stats":{"total_commits":642,"total_committers":4,"mean_commits":160.5,"dds":0.06074766355140182,"last_synced_commit":"eeee63932b3b3baa348c11335c8d642fb6e4fd4d"},"previous_names":[],"tags_count":163,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndyObtiva%2Fglimmer-dsl-libui","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndyObtiva%2Fglimmer-dsl-libui/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndyObtiva%2Fglimmer-dsl-libui/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndyObtiva%2Fglimmer-dsl-libui/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AndyObtiva","download_url":"https://codeload.github.com/AndyObtiva/glimmer-dsl-libui/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225934405,"owners_count":17547738,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["desktop","dsl","dsl-syntax","framework","glimmer","glimmer-dsl","gui","libui","ruby","ruby-gem","ruby-library","rubygem"],"created_at":"2024-08-06T08:01:38.492Z","updated_at":"2025-07-16T14:30:46.085Z","avatar_url":"https://github.com/AndyObtiva.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# [\u003cimg src=\"https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png\" height=85 /\u003e](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.12.8\n## Prerequisite-Free Ruby Desktop Development Cross-Platform Native GUI Library  ([Fukuoka Award Winning](https://andymaleh.blogspot.com/2022/02/glimmer-dsl-for-libui-wins-fukuoka-ruby.html))\n### The Quickest Way From Zero To GUI\n[![Gem Version](https://badge.fury.io/rb/glimmer-dsl-libui.svg)](http://badge.fury.io/rb/glimmer-dsl-libui)\n[![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n\n**[If You Liked Shoes, You'll Love Glimmer!](https://github.com/AndyObtiva/glimmer#faq)**\n\n(**[Fukuoka Ruby Award Competition 2022 Special Award Winner](https://andymaleh.blogspot.com/2022/02/glimmer-dsl-for-libui-wins-fukuoka-ruby.html)**)\n\n(**[***RubyConf 2024 Workshop - How To Build Basic Desktop Applications in Ruby***](https://www.youtube.com/watch?v=TTSqRdTVtDY)**)\n\n(**[***RubyConf 2023 Workshop - How To Build Desktop Applications in Ruby***](https://github.com/AndyObtiva/how-to-build-desktop-applications-in-ruby)**)\n\n(**[***RubyConf 2022 Talk - Building Native GUI Apps in Ruby***](https://andymaleh.blogspot.com/2023/02/rubyconf-2022-talk-video-for-building.html)**)\n\n[**(Ruby Rogues Podcast Interview - Desktop Apps in Ruby ft. Andy)**](https://andymaleh.blogspot.com/2022/05/ruby-rogues-podcast-interview-desktop.html)\n\n[Glimmer](https://github.com/AndyObtiva/glimmer) DSL for [LibUI](https://github.com/libui-ng/libui-ng) is a [Fukuoka Award Winning](https://andymaleh.blogspot.com/2022/02/glimmer-dsl-for-libui-wins-fukuoka-ruby.html) prerequisite-free [MRI Ruby](https://www.ruby-lang.org) desktop development cross-platform native GUI (Graphical User Interface) library. No need to pre-install any prerequisites. Just install the [gem](https://rubygems.org/gems/glimmer-dsl-libui) and have cross-platform native GUI that just works on Mac, Windows, and Linux!\n\nMac | Windows | Linux\n----|---------|------\n![glimmer-dsl-libui-mac-control-gallery.png](images/glimmer-dsl-libui-mac-control-gallery.png) | ![glimmer-dsl-libui-windows-control-gallery.png](images/glimmer-dsl-libui-windows-control-gallery.png) | ![glimmer-dsl-libui-linux-control-gallery.png](images/glimmer-dsl-libui-linux-control-gallery.png)\n\n[LibUI](https://github.com/libui-ng/libui-ng) is 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/)). Applications built with Glimmer DSL for LibUI will provide the familiar native look, feel, and behavior of GUI on Mac, Windows, and Linux.\n\nThe 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/libui-ng/libui-ng) 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 Ruby and 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.\n\n[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:\n- [Declarative DSL syntax](#glimmer-gui-dsl-concepts) that visually maps to the GUI control hierarchy\n- [Convention over configuration](#smart-defaults-and-conventions) via smart defaults and automation of low-level details\n- Requiring the [least amount of syntax](#glimmer-gui-dsl-concepts) possible to build GUI\n- [Custom Component](#custom-components) support (Custom Controls, Custom Windows, and Custom Shapes), including [Component Slots](#class-based-custom-control-slots) and external Ruby gems (e.g. [Graphs and Charts](https://github.com/AndyObtiva/glimmer-libui-cc-graphs_and_charts))\n- [Bidirectional/Unidirectional Data-Binding](#data-binding) to declaratively wire and automatically synchronize GUI Views with Models\n- [Scaffolding](#scaffold-application) for new custom windows/controls, apps, and gems\n- [Far Future Plan] Native-Executable [packaging](#packaging) on Mac, Windows, and Linux.\n\nHello, World!\n\n```ruby\nrequire 'glimmer-dsl-libui'\n\ninclude Glimmer\n\nwindow('hello world').show\n```\n\nMac | Windows | Linux\n----|---------|------\n![glimmer-dsl-libui-mac-basic-window.png](images/glimmer-dsl-libui-mac-basic-window.png) | ![glimmer-dsl-libui-windows-basic-window.png](images/glimmer-dsl-libui-windows-basic-window.png) | ![glimmer-dsl-libui-linux-basic-window.png](images/glimmer-dsl-libui-linux-basic-window.png)\n\nBasic Button\n\n```ruby\nrequire 'glimmer-dsl-libui'\n\ninclude Glimmer\n\nwindow('hello world', 300, 200) {\n  button('Button') {\n    on_clicked do\n      msg_box('Information', 'You clicked the button')\n    end\n  }\n}.show\n```\n\nBasic Table Progress Bar\n\nMac | Windows | Linux\n----|---------|------\n![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) | ![glimmer-dsl-libui-windows-basic-button.png](/images/glimmer-dsl-libui-windows-basic-button.png) ![glimmer-dsl-libui-windows-basic-button-msg-box.png](/images/glimmer-dsl-libui-windows-basic-button-msg-box.png) | ![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)\n\n```ruby\nrequire 'glimmer-dsl-libui'\n\ninclude Glimmer\n\ndata = [\n  ['task 1', 0],\n  ['task 2', 15],\n  ['task 3', 100],\n  ['task 4', 75],\n  ['task 5', -1],\n]\n\nwindow('Task Progress', 300, 200) {\n  vertical_box {\n    table {\n      text_column('Task')\n      progress_bar_column('Progress')\n\n      cell_rows data # implicit data-binding\n    }\n    \n    button('Mark All As Done') {\n      stretchy false\n      \n      on_clicked do\n        data.each_with_index do |row_data, row|\n          data[row][1] = 100 # automatically updates table due to implicit data-binding\n        end\n      end\n    }\n  }\n}.show\n```\n\nMac | Windows | Linux\n----|---------|------\n![glimmer-dsl-libui-mac-basic-table-progress-bar.png](images/glimmer-dsl-libui-mac-basic-table-progress-bar.png) | ![glimmer-dsl-libui-windows-basic-table-progress-bar.png](images/glimmer-dsl-libui-windows-basic-table-progress-bar.png) | ![glimmer-dsl-libui-linux-basic-table-progress-bar.png](images/glimmer-dsl-libui-linux-basic-table-progress-bar.png)\n\nForm Table\n\n```ruby\nrequire 'glimmer-dsl-libui'\n\nclass FormTable\n  Contact = Struct.new(:name, :email, :phone, :city, :state)\n  \n  include Glimmer\n  \n  attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value\n  \n  def initialize\n    @contacts = [\n      Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),\n      Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),\n      Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),\n      Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),\n      Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),\n    ]\n  end\n  \n  def launch\n    window('Contacts', 600, 600) {\n      margined true\n      \n      vertical_box {\n        form {\n          stretchy false\n          \n          entry {\n            label 'Name'\n            text \u003c=\u003e [self, :name] # bidirectional data-binding between entry text and self.name\n          }\n          \n          entry {\n            label 'Email'\n            text \u003c=\u003e [self, :email]\n          }\n          \n          entry {\n            label 'Phone'\n            text \u003c=\u003e [self, :phone]\n          }\n          \n          entry {\n            label 'City'\n            text \u003c=\u003e [self, :city]\n          }\n          \n          entry {\n            label 'State'\n            text \u003c=\u003e [self, :state]\n          }\n        }\n        \n        button('Save Contact') {\n          stretchy false\n          \n          on_clicked do\n            new_row = [name, email, phone, city, state]\n            if new_row.map(\u0026:to_s).include?('')\n              msg_box_error('Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')\n            else\n              @contacts \u003c\u003c Contact.new(*new_row) # automatically inserts a row into the table due to explicit data-binding\n              @unfiltered_contacts = @contacts.dup\n              self.name = '' # automatically clears name entry through explicit data-binding\n              self.email = ''\n              self.phone = ''\n              self.city = ''\n              self.state = ''\n            end\n          end\n        }\n        \n        search_entry {\n          stretchy false\n          # bidirectional data-binding of text to self.filter_value with after_write option\n          text \u003c=\u003e [self, :filter_value,\n            after_write: -\u003e(filter_value) { # execute after write to self.filter_value\n              @unfiltered_contacts ||= @contacts.dup\n              # Unfilter first to remove any previous filters\n              self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding\n              # Now, apply filter if entered\n              unless filter_value.empty?\n                self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding\n                  contact.members.any? do |attribute|\n                    contact[attribute].to_s.downcase.include?(filter_value.downcase)\n                  end\n                end\n              end\n            }\n          ]\n        }\n        \n        table {\n          text_column('Name')\n          text_column('Email')\n          text_column('Phone')\n          text_column('City')\n          text_column('State')\n    \n          editable true\n          cell_rows \u003c=\u003e [self, :contacts] # explicit data-binding to self.contacts Modal Array, auto-inferring model attribute names from underscored table column names by convention\n          \n          on_changed do |row, type, row_data|\n            puts \"Row #{row} #{type}: #{row_data}\"\n            $stdout.flush # for Windows\n          end\n          \n          on_edited do |row, row_data| # only fires on direct table editing\n            puts \"Row #{row} edited: #{row_data}\"\n            $stdout.flush # for Windows\n          end\n        }\n      }\n    }.show\n  end\nend\n\nFormTable.new.launch\n```\n\nMac | Windows | Linux\n----|---------|------\n![glimmer-dsl-libui-mac-form-table.png](images/glimmer-dsl-libui-mac-form-table.png) | ![glimmer-dsl-libui-windows-form-table.png](images/glimmer-dsl-libui-windows-form-table.png) | ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png)\n\nArea Gallery\n\n```ruby\nrequire 'glimmer-dsl-libui'\n\ninclude Glimmer\n\nwindow('Area Gallery', 400, 400) {\n  area {\n    path { # declarative stable path (explicit path syntax for multiple shapes sharing attributes)\n      square(0, 0, 100)\n      square(100, 100, 400)\n      \n      fill r: 102, g: 102, b: 204\n    }\n    \n    path { # declarative stable path (explicit path syntax for multiple shapes sharing attributes)\n      rectangle(0, 100, 100, 400)\n      rectangle(100, 0, 400, 100)\n      \n      # linear gradient (has x0, y0, x1, y1, and stops)\n      fill x0: 10, y0: 10, x1: 350, y1: 350, stops: [{pos: 0.25, r: 204, g: 102, b: 204}, {pos: 0.75, r: 102, g: 102, b: 204}]\n    }\n    \n    polygon(100, 100, 100, 400, 400, 100, 400, 400) { # declarative stable path (implicit path syntax for a single shape nested directly under area)\n      fill r: 202, g: 102, b: 104, a: 0.5\n      stroke r: 0, g: 0, b: 0\n    }\n    \n    polybezier(0, 0,\n               200, 100, 100, 200, 400, 100,\n               300, 100, 100, 300, 100, 400,\n               100, 300, 300, 100, 400, 400) { # declarative stable path (implicit path syntax for a single shape nested directly under area)\n      fill r: 202, g: 102, b: 204, a: 0.5\n      stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0\n    }\n    \n    polyline(100, 100, 400, 100, 100, 400, 400, 400, 0, 0) { # declarative stable path (implicit path syntax for a single shape nested directly under area)\n      stroke r: 0, g: 0, b: 0, thickness: 2\n    }\n    \n    arc(404, 216, 190, 90, 90, false) { # declarative stable path (implicit path syntax for a single shape nested directly under area)\n      # radial gradient (has an outer_radius in addition to x0, y0, x1, y1, and stops)\n      fill outer_radius: 90, x0: 0, y0: 0, x1: 500, y1: 500, stops: [{pos: 0.25, r: 102, g: 102, b: 204, a: 0.5}, {pos: 0.75, r: 204, g: 102, b: 204}]\n      stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0\n    }\n    \n    circle(200, 200, 90) { # declarative stable path (implicit path syntax for a single shape nested directly under area)\n      fill r: 202, g: 102, b: 204, a: 0.5\n      stroke r: 0, g: 0, b: 0, thickness: 2\n    }\n    \n    text(161, 40, 100) { # declarative stable text\n      string('Area Gallery') {\n        font family: 'Arial', size: (OS.mac? ? 14 : 11)\n        color :black\n      }\n    }\n    \n    on_mouse_event do |area_mouse_event|\n      p area_mouse_event\n    end\n    \n    on_mouse_moved do |area_mouse_event|\n      puts 'moved'\n    end\n    \n    on_mouse_down do |area_mouse_event|\n      puts 'mouse down'\n    end\n    \n    on_mouse_up do |area_mouse_event|\n      puts 'mouse up'\n    end\n    \n    on_mouse_drag_started do |area_mouse_event|\n      puts 'drag started'\n    end\n    \n    on_mouse_dragged do |area_mouse_event|\n      puts 'dragged'\n    end\n    \n    on_mouse_dropped do |area_mouse_event|\n      puts 'dropped'\n    end\n    \n    on_mouse_entered do\n      puts 'entered'\n    end\n    \n    on_mouse_exited do\n      puts 'exited'\n    end\n    \n    on_key_event do |area_key_event|\n      p area_key_event\n    end\n    \n    on_key_up do |area_key_event|\n      puts 'key up'\n    end\n    \n    on_key_down do |area_key_event|\n      puts 'key down'\n    end\n  }\n}.show\n```\n\nMac | Windows | Linux\n----|---------|------\n![glimmer-dsl-libui-mac-area-gallery.png](images/glimmer-dsl-libui-mac-area-gallery.png) | ![glimmer-dsl-libui-windows-area-gallery.png](images/glimmer-dsl-libui-windows-area-gallery.png) | ![glimmer-dsl-libui-linux-area-gallery.png](images/glimmer-dsl-libui-linux-area-gallery.png)\n\n[Check out many more examples over here!](#examples)\n\n[![glimmer-dsl-libui-mac-snake.gif](images/glimmer-dsl-libui-mac-snake.gif)](/docs/examples/GLIMMER-DSL-LIBUI-ADVANCED-EXAMPLES.md#snake)\n\n[![glimmer-dsl-libui-mac-color-the-circles.gif](images/glimmer-dsl-libui-mac-color-the-circles.gif)](/docs/examples/GLIMMER-DSL-LIBUI-ADVANCED-EXAMPLES.md#color-the-circles)\n\n[![glimmer-dsl-libui-mac-tetris.gif](images/glimmer-dsl-libui-mac-tetris.gif)](/docs/examples/GLIMMER-DSL-LIBUI-ADVANCED-EXAMPLES.md#tetris)\n\n[Check out many real apps built with Glimmer DSL for LibUI over here!](#applications)\n\n![kuiq](https://raw.githubusercontent.com/mperham/kuiq/main/misc/ui.png)\n\n![rubio-radio](https://raw.githubusercontent.com/kojix2/rubio-radio/main/screenshots/rubio-radio-linux.png)\n\n![Ruby Go](/images/glimmer-dsl-libui-mac-application-ruby-go.png)\n\nNOTE: [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) is regularly catching up with changes in the C [libui-ng](https://github.com/libui-ng/libui-ng) library API and in beta mode. The C [libui-ng](https://github.com/libui-ng/libui-ng) is still mid-alpha, which is why [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) cannot be declared v1.0.0 yet. Please help make better by contributing, adopting for small or low risk projects, and providing feedback. The more feedback and issues you report the better.\n\nLearn more about the differences between various [Glimmer](https://github.com/AndyObtiva/glimmer) DSLs by looking at the **[Glimmer DSL Comparison Table](https://github.com/AndyObtiva/glimmer#glimmer-dsl-comparison-table)**.\n\n## Table of Contents\n\n- [Glimmer DSL for LibUI](#)\n  - [Setup](#setup)\n  - [Usage](#usage)\n    - [Experimentation Usage](#experimentation-usage)\n    - [Prototyping Usage](#prototyping-usage)\n    - [Serious Usage](#serious-usage)\n  - [Glimmer Command](#glimmer-command)\n    - [Run Application](#run-application)\n    - [Run Examples](#run-examples)\n    - [Scaffold Application](#scaffold-application)\n    - [Scaffold Custom Control](#scaffold-custom-control)\n    - [Scaffold Custom Window](#scaffold-custom-window)\n    - [Scaffold Custom Shape](#scaffold-custom-shape)\n    - [Scaffold Custom Control Gem](#scaffold-custom-control-gem)\n    - [Scaffold Custom Window Gem](#scaffold-custom-window-gem)\n    - [Scaffold Custom Shape Gem](#scaffold-custom-shape-gem)\n    - [List Custom Control Gems](#list-custom-control-gems)\n    - [List Custom Window Gems](#list-custom-window-gems)\n    - [List Custom Shape Gems](#list-custom-shape-gems)\n    - [List Glimmer DSLs](#list-glimmer-dsls)\n  - [Girb (Glimmer IRB)](#girb-glimmer-irb)\n  - [Glimmer GUI DSL Concepts](#glimmer-gui-dsl-concepts)\n  - [API](#api)\n    - [Supported Keywords](#supported-keywords)\n    - [Common Control Properties](#common-control-properties)\n    - [Common Control Operations](#common-control-operations)\n    - [LibUI Operations](#libui-operations)\n    - [Extra Dialogs](#extra-dialogs)\n    - [Extra Operations](#extra-operations)\n    - [Table API](#table-api)\n    - [Area API](#area-api)\n      - [Scrolling Area](#scrolling-area)\n      - [Area Path Shapes](#area-path-shapes)\n      - [Area Text](#area-text)\n      - [Area Image](#area-image)\n      - [Colors](#colors)\n      - [Area Draw Params](#area-draw-params)\n      - [Area Listeners](#area-listeners)\n      - [Area Methods/Attributes](#area-methods-attributes)\n      - [Area Transform Matrix](#area-transform-matrix)\n      - [Area Composite Shape](#area-composite-shape)\n      - [Area Animation](#area-animation)\n    - [Smart Defaults and Conventions](#smart-defaults-and-conventions)\n    - [Custom Components](#custom-components)\n      - [Method-Based Custom Components](#method-based-custom-components)\n      - [Class-Based Custom Components](#class-based-custom-components)\n    - [Observer Pattern](#observer-pattern)\n    - [Data-Binding](#data-binding)\n      - [Bidirectional (Two-Way) Data-Binding](#bidirectional-two-way-data-binding)\n        - [Table Data-Binding](#table-data-binding)\n      - [Unidirectional (One-Way) Data-Binding](#unidirectional-one-way-data-binding)\n      - [Data-Binding API](#data-binding-api)\n      - [Data-Binding Gotchas](#data-binding-gotchas)\n    - [API Gotchas](#api-gotchas)\n    - [Original API](#original-api)\n  - [Packaging](#packaging)\n  - [Glimmer Style Guide](#glimmer-style-guide)\n  - [Examples](#examples)\n    - [Basic Examples](#basic-examples)\n    - [Advanced Examples](#advanced-examples)\n  - [Applications](#applications)\n    - [Manga2PDF](#manga2pdf)\n    - [Befunge98 GUI](#befunge98-gui)\n    - [i3off Gtk Ruby](#i3off-gtk-ruby)\n    - [Chess](#chess)\n    - [RubyCrumbler](#rubycrumbler)\n    - [Rubio-Radio](#rubio-radio)\n    - [PMV Calc](#pmv-calc)\n  - [Design Principles](#design-principles)\n  - [Glimmer Process](#glimmer-process)\n  - [Resources](#resources)\n  - [Help](#help)\n    - [Issues](#issues)\n    - [Chat](#chat)\n  - [Planned Features and Feature Suggestions](#planned-features-and-feature-suggestions)\n  - [Change Log](#change-log)\n  - [Contributing](#contributing)\n  - [Contributors](#contributors)\n  - [License](#license)\n\n## Setup\n\nNote: the newest Ruby 3.3 is not fully supported yet.\n\nInstall [glimmer-dsl-libui](https://rubygems.org/gems/glimmer-dsl-libui) gem directly into a [maintained Ruby version](https://www.ruby-lang.org/en/downloads/):\n\n```\ngem install glimmer-dsl-libui\n```\n \nOr install via Bundler `Gemfile`:\n\n```ruby\ngem 'glimmer-dsl-libui', '~\u003e 0.12.8'\n```\n\nTest that installation worked by running the [Glimmer Meta-Example](#examples):\n\n```\nglimmer examples\n```\n\nOr alternatively, run using the explicit Ruby command:\n\n```\nruby -r glimmer-dsl-libui -e \"require 'examples/meta_example'\"\n```\n\nMac | Windows | Linux\n----|---------|------\n![glimmer-dsl-libui-mac-meta-example.png](images/glimmer-dsl-libui-mac-meta-example.png) | ![glimmer-dsl-libui-windows-meta-example.png](images/glimmer-dsl-libui-windows-meta-example.png) | ![glimmer-dsl-libui-linux-meta-example.png](images/glimmer-dsl-libui-linux-meta-example.png)\n\n## Usage\n\nStart by requiring the [glimmer-dsl-libui](https://rubygems.org/gems/glimmer-dsl-libui) Ruby gem, whether through a Ruby `require 'glimmer-dsl-libui` statement or `Bundler`.\n\nAfterwards, to access the Glimmer GUI DSL:\n- If you are learning/experimenting/prototyping with Glimmer DSL for LibUI, include the `Glimmer` module into the top-level scope or a Ruby class.\n- If you are building a serious application, include `Glimmer::LibUI::Application` into the main view Ruby class\n- If you are building a custom control, include [`Glimmer::LibUI::CustomControl`](#custom-components) into a Ruby class\n- If you are building a cusotm window, include [`Glimmer::LibUI::CustomWindow`](#custom-components) into a Ruby class\n- If you are building a custom shape, include [`Glimmer::LibUI::CustomShape`](#custom-components) into a Ruby class.\n\nYou may learn more about the different options above with basic examples in the following subsections: [Experimentation Usage](#experimentation-usage), [Prototyping Usage](#prototyping-usage), [Serious Usage](#serious-usage).\n\nIf you are new to [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) (beginner), after going through the subsections below, check out the RubyConf 2022 talk [\"Building Native GUI Apps in Ruby\"](https://andymaleh.blogspot.com/2023/02/rubyconf-2022-talk-video-for-building.html), [Glimmer GUI DSL Concepts](#glimmer-gui-dsl-concepts), [Glimmer Style Guide](#glimmer-style-guide), [Glimmer Command](#glimmer-command) (just the basics, how to run an app, and how to run examples to start), [Girb](#girb-glimmer-irb) and [Examples](#examples) to quickly learn through copy/paste. It is very important for beginners to go through all the [Examples](#examples) from the most basic to the most advanced while reading the README topics that relate to the examples. Alternatively, beginners can learn from the RubyConf 2023 workshop [\"How To Build Desktop Applications in Ruby\"](https://github.com/AndyObtiva/how-to-build-desktop-applications-in-ruby), which includes 27 step-by-step exercises. You may refer to the [API](#api) once you have gotten your feet wet with [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) and need a more detailed reference.\n\nIf you encounter any issues with the documentation, get stuck with code you do not understand, or notice some out-of-date information, you may contact the project maintainers on the [Glimmer Gitter Chat](https://app.gitter.im/#/room/#AndyObtiva_glimmer:gitter.im). Also, this could be your opportunity to be a good steward of Open-Source Software by contributing a documentation fix in a GitHub Pull Request or reporting a GitHub Issue at least.\n\nFor integration with a Relational Database (SQL) via ActiveRecord, you may refer to the blog post that was written about [using ActiveRecord with SQLite DB and Glimmer DSL for SWT](https://andymaleh.blogspot.com/2022/06/using-activerecord-with-sqlite-db-in.html) (altering to fit CRuby and Glimmer DSL for LibUI). Also, [@chip](https://github.com/chip) created a prototype Git repo for starting a Glimmer DSL for LibUI project with SQLite DB and ActiveRecord: https://github.com/chip/glimmer_dsl_with_active_record\n\n### Experimentation Usage\n \nFor experimenting and learning, add `include Glimmer` into the top-level main object and start using the Glimmer GUI DSL directly.\n\nExample including `Glimmer` at the top-level scope just for some prototyping/demoing/testing (you may copy/paste in [`girb`](#girb-glimmer-irb)):\n\n```ruby\nrequire 'glimmer-dsl-libui'\n\ninclude Glimmer\n  \nwindow('hello world', 300, 200) {\n  button('Button') {\n    on_clicked do\n      puts 'Button Clicked'\n    end\n  }\n}.show\n```\n\n![usage mac](images/glimmer-dsl-libui-mac-usage.png)\n\n### Prototyping Usage\n\nFor prototyping, add `include Glimmer` into an actual class and start using the Glimmer GUI DSL in instance methods.\n\nExample including `Glimmer` and manually implementing the `#launch` method (you may copy/paste in [`girb`](#girb-glimmer-irb)):\n\n```ruby\nrequire 'glimmer-dsl-libui'\n\nclass SomeGlimmerApp\n  include Glimmer\n  \n  def launch\n    window('hello world', 300, 200) {\n      button('Button') {\n        on_clicked do\n          puts 'Button Clicked'\n        end\n      }\n    }.show\n  end\nend\n\nSomeGlimmerApp.new.launch\n```\n\n![usage mac](images/glimmer-dsl-libui-mac-usage.png)\n\n### Serious Usage\n\nFor more serious usage, add `include Glimmer::LibUI::Application` into an actual class (it automatically includes the `Glimmer` module) to conveniently declare the GUI underneath a `body` block (with the option of implementing `before_body` and `after_body` hooks) and take advantage of the inherited `SomeClass::launch` method implementation that automatically calls `window.show` for you.\n\nExample including `Glimmer::LibUI::Application` (you may copy/paste in [`girb`](#girb-glimmer-irb)):\n\n```ruby\nrequire 'glimmer-dsl-libui'\n\nclass SomeGlimmerApp\n  include Glimmer::LibUI::Application\n  \n  body {\n    window('hello world', 300, 200) {\n      button('Button') {\n        on_clicked do\n          puts 'Button Clicked'\n        end\n      }\n    }\n  }\nend\n\nSomeGlimmerApp.launch\n```\n\n![usage mac](images/glimmer-dsl-libui-mac-usage.png)\n\n(note: `Glimmer::LibUI::Application` is an alias for `Glimmer::LibUI::CustomWindow` since that is what it represents)\n\n## Glimmer Command\n\nThe `glimmer` command allows you to conveniently run applications (`glimmer app_path`), run examples (`glimmer examples`), and scaffold applications (`glimmer \"scaffold[app_name]\"`).\n\nYou can bring up usage instructions by running the `glimmer` command without arguments:\n\n```\nglimmer\n```\n\n```\nGlimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development Cross-Platform Native GUI Library) - Ruby Gem: glimmer-dsl-libui v0.8.0\n\nUsage: glimmer [--bundler] [--pd] [--quiet] [--debug] [--log-level=VALUE] [[ENV_VAR=VALUE]...] [[-ruby-option]...] (application.rb or task[task_args])\n\nRuns Glimmer applications and tasks.\n\nWhen applications are specified, they are run using Ruby,\nautomatically preloading the glimmer-dsl-libui Ruby gem.\n\nOptionally, extra Glimmer options, Ruby options, and/or environment variables may be passed in.\n\nGlimmer options:\n- \"--bundler=GROUP\"   : Activates gems in Bundler default group in Gemfile\n- \"--pd=BOOLEAN\"      : Requires puts_debuggerer to enable pd method\n- \"--quiet=BOOLEAN\"   : Does not announce file path of Glimmer application being launched\n- \"--debug\"           : Displays extra debugging information and enables debug logging\n- \"--log-level=VALUE\" : Sets Glimmer's Ruby logger level (\"ERROR\" / \"WARN\" / \"INFO\" / \"DEBUG\"; default is none)\n\nTasks are run via rake. Some tasks take arguments in square brackets (surround with double-quotes if using Zsh).\n\nAvailable tasks are below (if you do not see any, please add `require 'glimmer/rake_task'` to Rakefile and rerun or run rake -T):\n\nSelect a Glimmer task to run: (Press ↑/↓ arrow to move, Enter to select and letters to filter)\n‣ glimmer examples                                            # Brings up the Glimmer Meta-Sample app to allow browsing, running, and viewing code of Glimmer samples\n  glimmer list:gems:customcontrol[query]                      # List Glimmer custom control gems available at rubygems.org (query is optional) [alt: list:gems:cc]\n  glimmer list:gems:customshape[query]                        # List Glimmer custom shape gems available at rubygems.org (query is optional) [alt: list:gems:cs]\n  glimmer list:gems:customwindow[query]                       # List Glimmer custom window gems available at rubygems.org (query is optional) [alt: list:gems:cw]\n  glimmer list:gems:dsl[query]                                # List Glimmer DSL gems available at rubygems.org (query is optional)\n  glimmer run[app_path]                                       # Runs Glimmer app or custom window gem in the current directory, unless app_path is specified, then runs it instead (app_path is optional)\n  glimmer scaffold[app_name]                                  # Scaffold Glimmer application directory structure to build a new app\n  glimmer scaffold:customcontrol[name,namespace]              # Scaffold Glimmer::UI::CustomControl subclass (part of a view) under app/views (namespace is optional) [alt: scaffold:cc]\n  glimmer scaffold:customshape[name,namespace]                # Scaffold Glimmer::UI::CustomShape subclass (part of a view) under app/views (namespace is optional) [alt: scaffold:cs]\n  glimmer scaffold:customwindow[name,namespace]               # Scaffold Glimmer::UI::CustomWindow subclass (full window view) under app/views (namespace is optional) [alt: scaffold:cw]\n  glimmer scaffold:gem:customcontrol[name,namespace]          # Scaffold Glimmer::UI::CustomControl subclass (part of a view) under its own Ruby gem project (namespace is required) [alt: scaffold:gem:cc]\n  glimmer scaffold:gem:customshape[name,namespace]            # Scaffold Glimmer::UI::CustomShape subclass (part of a view) under its own Ruby gem project (namespace is required) [alt: scaffold:gem:cs]\n  glimmer scaffold:gem:customwindow[name,namespace]           # Scaffold Glimmer::UI::CustomWindow subclass (full window view) under its own Ruby gem + app project (namespace is required) [alt: scaffold:gem:cw]\n```\n\nOn Mac and Linux, it brings up a TUI (Text-based User Interface) for interactive navigation and execution of Glimmer tasks (courtesy of [rake-tui](https://github.com/AndyObtiva/rake-tui)).\n\nOn Windows and ARM64 machines, it simply lists the available Glimmer tasks at the end (courtsey of [rake](https://github.com/ruby/rake)).\n\nNote: If you encounter an issue running the `glimmer` command, run `bundle exec glimmer` instead.\n\n### Run Application\n\nRun Glimmer DSL for LibUI applications via this command:\n\n```\nglimmer app_path\n```\n\nFor example, from a cloned glimmer-dsl-libui repository:\n\n```\nglimmer examples/basic_window.rb\n```\n\nMac | Windows | Linux\n----|---------|------\n![glimmer-dsl-libui-mac-basic-window.png](/images/glimmer-dsl-libui-mac-basic-window.png) | ![glimmer-dsl-libui-windows-basic-window.png](/images/glimmer-dsl-libui-windows-basic-window.png) | ![glimmer-dsl-libui-linux-basic-window.png](/images/glimmer-dsl-libui-linux-basic-window.png)\n\n### Run Examples\n\nRun Glimmer DSL for LibUI included examples via this command:\n\n```\nglimmer examples\n```\n\nThat brings up the [Glimmer Meta-Example](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/examples/meta_example.rb))\n\nMac | Windows | Linux\n----|---------|------\n![glimmer-dsl-libui-mac-meta-example.png](images/glimmer-dsl-libui-mac-meta-example.png) | ![glimmer-dsl-libui-windows-meta-example.png](images/glimmer-dsl-libui-windows-meta-example.png) | ![glimmer-dsl-libui-linux-meta-example.png](images/glimmer-dsl-libui-linux-meta-example.png)\n\n### Scaffold Application\n\nApplication scaffolding enables automatically generating the directories/files of a new desktop GUI application that follows the MVC architecture and can be packaged as a Ruby gem that includes an executable script for running the app conveniently. It also ensures that software engineers follow the recommended Glimmer DSL for LibUI conventions and best practices. Application Scaffolding greatly improves software engineering productivity when building desktop applications with [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui).\n\nApplication Scaffolding relies on the juwelier Ruby gem, which expects a local Git config of [`user.name`](https://docs.github.com/en/get-started/getting-started-with-git/setting-your-username-in-git#setting-your-git-username-for-every-repository-on-your-computer) (`git config --global user.name \"FirstName LastName\"`) and `github.user` (`git config --global github.user githubusername`).\n\nScaffold Glimmer DSL for LibUI application with this command:\n\n```\nglimmer \"scaffold[app_name]\"\n```\n\nThat will automatically generate the general MVC structure of a new Glimmer DSL for LibUI application and launch the application when done.\n\nFor example, if we run:\n\n```\nglimmer \"scaffold[hello_world]\"\n```\n\nThe following files are generated and reported by the `glimmer` command:\n\n```\nCreated hello_world/.gitignore\nCreated hello_world/.ruby-version\nCreated hello_world/.ruby-gemset\nCreated hello_world/VERSION\nCreated hello_world/LICENSE.txt\nCreated hello_world/Gemfile\nCreated hello_world/Rakefile\nCreated hello_world/app/hello_world.rb\nCreated hello_world/app/hello_world/view/hello_world.rb\nCreated hello_world/app/hello_world/model/greeting.rb\nCreated hello_world/icons/windows/Hello World.ico\nCreated hello_world/icons/macosx/Hello World.icns\nCreated hello_world/icons/linux/Hello World.png\nCreated hello_world/app/hello_world/launch.rb\nCreated hello_world/bin/hello_world\n```\n\nThey include a basic Hello, World! application with menus and about/preferences dialogs.\n\nViews live under `app/app_name/view` (e.g. `app/hello_world/view`)\n\nModels live under `app/app_name/model` (e.g. `app/hello_world/model`)\n\nThe application runs automatically once scaffolding is done.\n\n![glimmer-dsl-libui-mac-scaffold-app-initial-screen.png](images/glimmer-dsl-libui-mac-scaffold-app-initial-screen.png)\n\n![glimmer-dsl-libui-mac-scaffold-app-preferences.png](images/glimmer-dsl-libui-mac-scaffold-app-preferences.png)\n\n![glimmer-dsl-libui-mac-scaffold-app-changed-greeting.png](images/glimmer-dsl-libui-mac-scaffold-app-changed-greeting.png)\n\n![glimmer-dsl-libui-mac-scaffold-app-about.png](images/glimmer-dsl-libui-mac-scaffold-app-about.png)\n\nOnce you step into the application directory, you can run it in one of multiple ways:\n\n```\nbin/app_name\n```\n\nFor example:\n\n```\nbin/hello_world\n```\n\nOr using the Glimmer generic command for running applications, which will automatically detect the application running script:\n\n```\nglimmer run\n```\n\n![glimmer-dsl-libui-mac-scaffold-app-initial-screen.png](images/glimmer-dsl-libui-mac-scaffold-app-initial-screen.png)\n\nThe application comes with the [juwelier](https://rubygems.org/gems/juwelier) gem for auto-generating an application gem from the app `Rakefile` and `Gemfile` configuration (no need to manually declare gems in a gemspec... just use `Gemfile` normally and [juwelier](https://rubygems.org/gems/juwelier) takes care of the rest by generating an app gemspec automatically from `Gemfile`).\n\nYou can package the newly scaffolded app as a Ruby gem by running this command:\n\n```\nglimmer package:gem\n```\n\nOr by using the raw rake command:\n\n```\nrake build\n```\n\nYou can generate the application gemspec explicitly if needed with this command (though it is not needed to build the gem):\n\n```\nglimmer package:gemspec\n```\n\nOr by using the raw rake command:\n\n```\nrake gemspec:generate\n```\n\nOnce you install the gem (e.g. `gem install hello_world`), you can simply run the app with its executable script:\n\n```\napp_name\n```\n\nFor example:\n\n```\nhello_world\n```\n\n![glimmer-dsl-libui-mac-scaffold-app-initial-screen.png](images/glimmer-dsl-libui-mac-scaffold-app-initial-screen.png)\n\n### Scaffold Custom Control\n\nWhen you are in a scaffolded application, you can scaffold a new [custom control](#custom-components) (a control that you can put anything in to represent a view concept in your application) by running this command:\n\n```\nglimmer \"scaffold:customcontrol[name,namespace]\"\n```\n\nThe `name` represents the [custom control](#custom-components) view class name (it can be underscored, and Glimmer will automatically classify it).\n\nThe `namespace` is optional and represents the module that the [custom control](#custom-components) view class will live under. If left off, the main application class namespace is used (e.g. the top-level `HelloWorld` class namespace for a `hello_world` application).\n\nYou can also use the shorter `cc` alias for `customcontrol`:\n\n```\nglimmer \"scaffold:cc[name,namespace]\"\n```\n\nFor example by running this command under a `hello_world` application:\n\n```\nglimmer \"scaffold:cc[model_form]\"\n```\n\nThat will generate this class under `app/hello_world/view/model_form`:\n\n```ruby\nclass HelloWorld\n  module View\n    class ModelForm\n      include Glimmer::LibUI::CustomControl\n  \n      ## Add options like the following to configure CustomControl by outside consumers\n      #\n      # options :custom_text, :background_color\n      # option :foreground_color, default: :red\n      \n      # Replace example options with your own options\n      option :model\n      option :attributes\n  \n      ## Use before_body block to pre-initialize variables to use in body\n      #\n      #\n      before_body do\n        # Replace example code with your own before_body code\n        default_model_attributes = [:first_name, :last_name, :email]\n        default_model_class = Struct.new(*default_model_attributes)\n        self.model ||= default_model_class.new\n        self.attributes ||= default_model_attributes\n      end\n  \n      ## Use after_body block to setup observers for controls in body\n      #\n      # after_body do\n      #\n      # end\n  \n      ## Add control content under custom control body\n      ##\n      ## If you want to add a window as the top-most control,\n      ## consider creating a custom window instead\n      ## (Glimmer::LibUI::CustomWindow offers window convenience methods, like show and hide)\n      #\n      body {\n        # Replace example content (model_form custom control) with your own custom control content.\n        form {\n          attributes.each do |attribute|\n            entry { |e|\n              label attribute.to_s.underscore.split('_').map(\u0026:capitalize).join(' ')\n              text \u003c=\u003e [model, attribute]\n            }\n          end\n        }\n      }\n  \n    end\n  end\nend\n```\n\nWhen the generated file is required in another view (e.g. `require 'hello_world/view/model_form'`), the [custom control](#custom-components) keyword `model_form` become available and reusable, like by calling:\n\n```ruby\nwindow {\n  vertical_box {\n    label('Form:')\n    model_form(model: some_model, attributes: array_of_attributes)\n  }\n}\n```\n\nHere is an example that generates a [custom control](#custom-components) with a namespace:\n\n```\nglimmer \"scaffold:cc[model_form,common]\"\n```\n\nThat will generate this class under `app/common/view/model_form`:\n\n```ruby\nmodule Common\n  module View\n    class ModelForm\n      include Glimmer::LibUI::CustomControl\n  \n      ## Add options like the following to configure CustomControl by outside consumers\n      #\n      # options :custom_text, :background_color\n      # option :foreground_color, default: :red\n      \n      # Replace example options with your own options\n      option :model\n      option :attributes\n  \n      ## Use before_body block to pre-initialize variables to use in body\n      #\n      #\n      before_body do\n        # Replace example code with your own before_body code\n        default_model_attributes = [:first_name, :last_name, :email]\n        default_model_class = Struct.new(*default_model_attributes)\n        self.model ||= default_model_class.new\n        self.attributes ||= default_model_attributes\n      end\n  \n      ## Use after_body block to setup observers for controls in body\n      #\n      # after_body do\n      #\n      # end\n  \n      ## Add control content under custom control body\n      ##\n      ## If you want to add a window as the top-most control,\n      ## consider creating a custom window instead\n      ## (Glimmer::LibUI::CustomWindow offers window convenience methods, like show and hide)\n      #\n      body {\n        # Replace example content (model_form custom control) with your own custom control content.\n        form {\n          attributes.each do |attribute|\n            entry { |e|\n              label attribute.to_s.underscore.split('_').map(\u0026:capitalize).join(' ')\n              text \u003c=\u003e [model, attribute]\n            }\n          end\n        }\n      }\n  \n    end\n  end\nend\n```\n\nWhen that file is required in another view (e.g. `require 'common/view/model_form'`), the `model_form` keyword becomes available:\n\n```ruby\nwindow {\n  vertical_box {\n    label('Form:')\n    model_form(model: some_model, attributes: array_of_attributes)\n  }\n}\n```\n\nIf for whatever reason, you end up with 2 [custom control](#custom-components) views having the same name with different namespaces, then you can invoke the specific [custom control](#custom-components) you want by including the Ruby namespace in underscored format separated by double-underscores:\n\n```ruby\nwindow {\n  vertical_box {\n    label('Form:')\n    common__view__model_form(model: some_model, attributes: array_of_attributes)\n  }\n}\n```\n\nOr another `model_form` [custom control](#custom-components) view:\n\n```ruby\nwindow {\n  vertical_box {\n    label('Form:')\n    hello_world__view__model_form(model: some_model, attributes: array_of_attributes)\n  }\n}\n```\n\n### Scaffold Custom Window\n\nA custom window is a specialization of a custom control that has a `window` as its `body` root.\n\nWhen you are in a scaffolded application, you can scaffold a new custom window (a window that you can put anything in to represent a view concept in your application) by running this command:\n\n```\nglimmer \"scaffold:customwindow[name,namespace]\"\n```\n\nThe `name` represents the custom window view class name (it can be underscored, and Glimmer will automatically classify it).\n\nThe `namespace` is optional and represents the module that the custom window view class will live under. If left off, the main application class namespace is used (e.g. the top-level `HelloWorld` class namespace for a `hello_world` application).\n\nYou can also use the shorter `cw` alias for `customwindow`:\n\n```\nglimmer \"scaffold:cw[name,namespace]\"\n```\n\nFor example by running this command under a `hello_world` application:\n\n```\nglimmer \"scaffold:cw[greeting_window]\"\n```\n\nThat will generate this class under `app/hello_world/view/greeting_window`:\n\n```ruby\nclass HelloWorld\n  module View\n    class GreetingWindow\n      include Glimmer::LibUI::CustomWindow\n    \n          \n      ## Add options like the following to configure CustomWindow by outside consumers\n      #\n      # options :title, :background_color\n      # option :width, default: 320\n      # option :height, default: 240\n  \n      ## Use before_body block to pre-initialize variables to use in body and\n      #  to setup application menu\n      #\n      # before_body do\n      #\n      # end\n  \n      ## Use after_body block to setup observers for controls in body\n      #\n      # after_body do\n      #\n      # end\n  \n      ## Add control content inside custom window body\n      ## Top-most control must be a window or another custom window\n      #\n      body {\n        window {\n          # Replace example content below with custom window content\n          content_size 240, 240\n          title 'Hello World'\n          \n          margined true\n          \n          label {\n            text 'Hello World'\n          }\n        }\n      }\n    end\n  end\nend\n```\n\nWhen the generated file is required in another view (e.g. `require 'hello_world/view/greeting_window'`), the custom window keyword `greeting_window` become available and reusable, like by calling:\n\n```ruby\ngreeting_window.show\n```\n\nHere is an example that generates a custom window with a namespace:\n\n```\nglimmer \"scaffold:cw[train,station]\"\n```\n\nThat will generate this class under `app/station/view/train`:\n\n```ruby\nmodule Station\n  module View\n    class Train\n      include Glimmer::LibUI::CustomWindow\n    \n          \n      ## Add options like the following to configure CustomWindow by outside consumers\n      #\n      # options :title, :background_color\n      # option :width, default: 320\n      # option :height, default: 240\n  \n      ## Use before_body block to pre-initialize variables to use in body and\n      #  to setup application menu\n      #\n      # before_body do\n      #\n      # end\n  \n      ## Use after_body block to setup observers for controls in body\n      #\n      # after_body do\n      #\n      # end\n  \n      ## Add control content inside custom window body\n      ## Top-most control must be a window or another custom window\n      #\n      body {\n        window {\n          # Replace example content below with custom window content\n          content_size 240, 240\n          title 'Station'\n          \n          margined true\n          \n          label {\n            text 'Station'\n          }\n        }\n      }\n    end\n  end\nend\n```\n\nWhen that file is required in another view (e.g. `require 'station/view/train'`), the `train` keyword becomes available:\n\n```ruby\ntrain.show\n```\n\nIf for whatever reason, you end up with 2 custom window views having the same name with different namespaces, then you can invoke the specific custom window you want by including the Ruby namespace in underscored format separated by double-underscores:\n\n```ruby\nstation__view__train.show\n```\n\nOr another `train` custom window view:\n\n```ruby\nhello_world__view__train.show\n```\n\n### Scaffold Custom Shape\n\nWhen you are in a scaffolded application, you can scaffold a new [custom shape](#custom-components) (a shape that you can put anything in to represent a view concept in your application) by running this command:\n\n```\nglimmer \"scaffold:customshape[name,namespace]\"\n```\n\nThe `name` represents the [custom shape](#custom-components) view class name (it can be underscored, and Glimmer will automatically classify it).\n\nThe `namespace` is optional and represents the module that the [custom shape](#custom-components) view class will live under. If left off, the main application class namespace is used (e.g. the top-level `HelloWorld` class namespace for a `hello_world` application).\n\nYou can also use the shorter `cs` alias for `customshape`:\n\n```\nglimmer \"scaffold:cs[name,namespace]\"\n```\n\nFor example by running this command under a `hello_world` application:\n\n```\nglimmer \"scaffold:cs[heart]\"\n```\n\nThat will generate this class under `app/hello_world/view/heart`:\n\n```ruby\nclass HelloWorld\n  module View\n    class Heart\n      include Glimmer::LibUI::CustomShape\n  \n      ## Add options like the following to configure CustomShape by outside consumers\n      #\n      # options :option1, option2, option3\n      option :background_color, default: :red\n      option :size_width, default: 100\n      option :size_height, default: 100\n      option :location_x, default: 0\n      option :location_y, default: 0\n  \n      ## Use before_body block to pre-initialize variables to use in body\n      #\n      #\n      # before_body do\n      #\n      # end\n  \n      ## Use after_body block to setup observers for shapes in body\n      #\n      # after_body do\n      #\n      # end\n  \n      ## Add shape content under custom shape body\n      #\n      body {\n        # Replace example content below (heart shape) with custom shape content\n        shape(location_x, location_y) {\n          # This fill color is shared under all direct children of `shape`\n          fill background_color\n          \n          bezier(\n            size_width - size_width*0.66, size_height/2 - size_height*0.33,\n            size_width*0.65 - size_width*0.66, 0 - size_height*0.33,\n            size_width/2 - size_width*0.66, size_height*0.75 - size_height*0.33,\n            size_width - size_width*0.66, size_height - size_height*0.33\n          )\n          \n          bezier(\n            size_width - size_width*0.66, size_height/2 - size_height*0.33,\n            size_width*1.35 - size_width*0.66, 0 - size_height*0.33,\n            size_width*1.5 - size_width*0.66, size_height*0.75 - size_height*0.33,\n            size_width - size_width*0.66, size_height - size_height*0.33\n          )\n        }\n      }\n  \n    end\n  end\nend\n```\n\nWhen the generated file is required in another view (e.g. `require 'hello_world/view/heart'`), the [custom shape](#custom-components) keyword `heart` become available and reusable, like by calling:\n\n```ruby\nwindow {\n  area {\n    heart\n  }\n}\n```\n\nYou can pass `heart` options (as defined with `option` near the top of the class):\n\n```ruby\nwindow {\n  area {\n    heart(location_x: 25, location_y: 50)\n  }\n}\n```\n\nHere is an example that generates a [custom shape](#custom-components) with a namespace:\n\n```\nglimmer \"scaffold:cs[heart,acme]\"\n```\n\nThat will generate this class under `app/acme/view/heart`:\n\n```ruby\nmodule Acme\n  module View\n    class Heart\n      include Glimmer::LibUI::CustomShape\n  \n      ## Add options like the following to configure CustomShape by outside consumers\n      #\n      # options :option1, option2, option3\n      option :background_color, default: :red\n      option :size_width, default: 100\n      option :size_height, default: 100\n      option :location_x, default: 0\n      option :location_y, default: 0\n  \n      ## Use before_body block to pre-initialize variables to use in body\n      #\n      #\n      # before_body do\n      #\n      # end\n  \n      ## Use after_body block to setup observers for shapes in body\n      #\n      # after_body do\n      #\n      # end\n  \n      ## Add shape content under custom shape body\n      #\n      body {\n        # Replace example content below (heart shape) with your own custom shape content\n        shape(location_x, location_y) {\n          # This fill color is shared under all direct children of `shape`\n          fill background_color\n          \n          bezier(\n            size_width - size_width*0.66, size_height/2 - size_height*0.33,\n            size_width*0.65 - size_width*0.66, 0 - size_height*0.33,\n            size_width/2 - size_width*0.66, size_height*0.75 - size_height*0.33,\n            size_width - size_width*0.66, size_height - size_height*0.33\n          )\n          \n          bezier(\n            size_width - size_width*0.66, size_height/2 - size_height*0.33,\n            size_width*1.35 - size_width*0.66, 0 - size_height*0.33,\n            size_width*1.5 - size_width*0.66, size_height*0.75 - size_height*0.33,\n            size_width - size_width*0.66, size_height - size_height*0.33\n          )\n        }\n      }\n  \n    end\n  end\nend\n```\n\nWhen that file is required in another view (e.g. `require 'acme/view/heart'`), the `heart` keyword becomes available:\n\n```ruby\nwindow {\n  area {\n    heart\n  }\n}\n```\n\nIf for whatever reason, you end up with 2 [custom shape](#custom-components) views having the same name with different namespaces, then you can invoke the specific [custom shape](#custom-components) you want by including the Ruby namespace in underscored format separated by double-underscores:\n\n```ruby\nwindow {\n  area {\n    acme__view__heart\n  }\n}\n```\n\nOr another `heart` [custom shape](#custom-components) view:\n\n```ruby\nwindow {\n  area {\n    hello_world__view__heart\n  }\n}\n```\n\n### Scaffold Custom Control Gem\n\nYou can scaffold a Ruby gem around a reusable [custom control](#custom-components) to expose publicly and make available for multiple projects by running this command:\n\n```\nglimmer \"scaffold:gem:customcontrol[name,namespace]\"\n```\n\nThat will generate a [custom control](#custom-components) gem project under the naming convention: `glimmer-libui-cc-name-namespace`\n\nThe naming convention helps with discoverability of Ruby gems using the command `glimmer list:gems:customcontrol[query]` (or alias: `glimmer list:gems:cc[query]`) where filtering `query` is optional.\n\nThe `name` is the [custom control](#custom-components) class name, which must not contain dashes by convention (multiple words can be concatenated or can use underscores between them).\n\nThe `namespace` is needed to avoid clashing with other [custom control](#custom-components) gems that other software engineers might have thought of. It is recommended not to include dashes between words in it by convention yet concatenated words or underscores between them.\n\nHere is a shorter alias for the [custom control](#custom-components) gem scaffolding command:\n\n```\nglimmer \"scaffold:gem:cc[name,namespace]\"\n```\n\nYou can package the newly scaffolded project as a Ruby gem by running this command:\n\n```\nglimmer package:gem\n```\n\nOr by using the raw rake command:\n\n```\nrake build\n```\n\nYou can generate the application gemspec explicitly if needed with this command (though it is not needed to build the gem):\n\n```\nglimmer package:gemspec\n```\n\nOr by using the raw rake command:\n\n```\nrake gemspec:generate\n```\n\nTypically, consumers of the gem would include it in their own project, which makes the gem keyword available in the Glimmer GUI DSL anywhere `Glimmer`. `Glimmer::LibUI::Application`, `Glimmer::LibUI::CustomWindow`, `Glimmer::LibUI::CustomControl`, or `Glimmer::LibUI::CustomShape` is mixed.\n\nFor example:\n\n```ruby\nrequire 'glimmer-libui-cc-model_form-acme'\n\n...\nwindow {\n  vertical_box {\n    label('Form:')\n    \n    model_form(model: some_model, attributes: some_attributes)\n  }\n}\n...\n```\n\nA real external Custom Control Gem is [Graphs and Charts](https://github.com/AndyObtiva/glimmer-libui-cc-graphs_and_charts).\n\n### Scaffold Custom Window Gem\n\nA custom window is a specialization of a custom control that has a `window` as its `body` root.\n\nYou can scaffold a Ruby gem around a reusable custom window to expose publicly and make available for multiple projects by running this command:\n\n```\nglimmer \"scaffold:gem:customwindow[name,namespace]\"\n```\n\nThat will generate a custom window gem project under the naming convention: `glimmer-libui-cw-name-namespace`\n\nThe naming convention helps with discoverability of Ruby gems using the command `glimmer list:gems:customwindow[query]` (or alias: `glimmer list:gems:cw[query]`) where filtering `query` is optional.\n\nThe `name` is the custom window class name, which must not contain dashes by convention (multiple words can be concatenated or can use underscores between them).\n\nThe `namespace` is needed to avoid clashing with other custom window gems that other software engineers might have thought of. It is recommended not to include dashes between words in it by convention yet concatenated words or underscores between them.\n\nHere is a shorter alias for the custom window gem scaffolding command:\n\n```\nglimmer \"scaffold:gem:cw[name,namespace]\"\n```\n\nYou can package the newly scaffolded project as a Ruby gem by running this command:\n\n```\nglimmer package:gem\n```\n\nOr by using the raw rake command:\n\n```\nrake build\n```\n\nYou can generate the application gemspec explicitly if needed with this command (though it is not needed to build the gem):\n\n```\nglimmer package:gemspec\n```\n\nOr by using the raw rake command:\n\n```\nrake gemspec:generate\n```\n\nThe project optionally allows you to run the custom window as its own separate app with a executable script (`bin/gem_name`) to see it, which helps with prototyping it.\n\nBut, typically consumers of the gem would include it in their own project, which makes the gem keyword available in the Glimmer GUI DSL anywhere `Glimmer`. `Glimmer::LibUI::Application`, `Glimmer::LibUI::CustomWindow`, `Glimmer::LibUI::CustomControl`, or `Glimmer::LibUI::CustomShape` is mixed.\n\nFor example:\n\n```ruby\nrequire 'glimmer-libui-cw-greeter-acme'\n\n...\ngreeter.show\n...\n```\n\n### Scaffold Custom Shape Gem\n\nYou can scaffold a Ruby gem around a reusable [custom shape](#custom-components) to expose publicly and make available for multiple projects by running this command:\n\n```\nglimmer \"scaffold:gem:customshape[name,namespace]\"\n```\n\nThat will generate a [custom shape](#custom-components) gem project under the naming convention: `glimmer-libui-cc-name-namespace`\n\nThe naming convention helps with discoverability of Ruby gems using the command `glimmer list:gems:customshape[query]` (or alias: `glimmer list:gems:cs[query]`) where filtering `query` is optional.\n\nThe `name` is the [custom shape](#custom-components) class name, which must not contain dashes by convention (multiple words can be concatenated or can use underscores between them).\n\nThe `namespace` is needed to avoid clashing with other [custom shape](#custom-components) gems that other software engineers might have thought of. It is recommended not to include dashes between words in it by convention yet concatenated words or underscores between them.\n\nHere is a shorter alias for the [custom shape](#custom-components) gem scaffolding command:\n\n```\nglimmer \"scaffold:gem:cs[name,namespace]\"\n```\n\nYou can package the newly scaffolded project as a Ruby gem by running this command:\n\n```\nglimmer package:gem\n```\n\nOr by using the raw rake command:\n\n```\nrake build\n```\n\nYou can generate the application gemspec explicitly if needed with this command (though it is not needed to build the gem):\n\n```\nglimmer package:gemspec\n```\n\nOr by using the raw rake command:\n\n```\nrake gemspec:generate\n```\n\nTypically, consumers of the gem would include it in their own project, which makes the gem keyword available in the Glimmer GUI DSL anywhere `Glimmer`. `Glimmer::LibUI::Application`, `Glimmer::LibUI::CustomWindow`, `Glimmer::LibUI::CustomControl`, or `Glimmer::LibUI::CustomShape` is mixed.\n\nFor example:\n\n```ruby\nrequire 'glimmer-libui-cs-heart-acme'\n\n...\nwindow {\n  area {\n    heart\n  }\n}\n...\n```\n\n### List Custom Control Gems\n\nCustom control gems are scaffolded to follow the naming convention: `glimmer-libui-cc-name-namespace`\n\nThe naming convention helps with discoverability of Ruby gems using the command:\n\n```\nglimmer list:gems:customcontrol[query]\n```\n\nOr by using the shorter alias:\n\n```\nglimmer list:gems:cc[query]\n```\n\nThe filtering `query` is optional.\n\n### List Custom Window Gems\n\nCustom window gems are scaffolded to follow the naming convention: `glimmer-libui-cw-name-namespace`\n\nThe naming convention helps with discoverability of Ruby gems using the command:\n\n```\nglimmer list:gems:customwindow[query]\n```\n\nOr by using the shorter alias:\n\n```\nglimmer list:gems:cw[query]\n```\n\nThe filtering `query` is optional.\n\n### List Custom Shape Gems\n\nCustom shape gems are scaffolded to follow the naming convention: `glimmer-libui-cs-name-namespace`\n\nThe naming convention helps with discoverability of Ruby gems using the command:\n\n```\nglimmer list:gems:customshape[query]\n```\n\nOr by using the shorter alias:\n\n```\nglimmer list:gems:cs[query]\n```\n\nThe filtering `query` is optional.\n\n### List Glimmer DSLs\n\nGlimmer DSLs can be listed with this command:\n\n```\nglimmer list:gems:dsl[query]\n```\n\nThe filtering `query` is optional.\n\n## Girb (Glimmer IRB)\n\nYou can run the `girb` command (`bin/girb` if you cloned the project locally) to do some quick and dirty experimentation and learning:\n\n```\ngirb\n```\n\nThis gives you `irb` with the `glimmer-dsl-libui` gem loaded and the `Glimmer` module mixed into the main object for easy experimentation with GUI.\n\n![glimmer-dsl-libui-girb.png](images/glimmer-dsl-libui-girb.png)\n\nFor a more advanced code editing tool, check out the [Meta-Example (The Example of Examples)](#examples).\n\nGotcha: On the Mac, when you close a window opened in `girb`, it remains open until you enter `exit` or open another GUI window.\n\n## Glimmer GUI DSL Concepts\n\nThe Glimmer GUI DSL provides object-oriented declarative hierarchical syntax for [LibUI](https://github.com/kojix2/LibUI) that:\n- Supports smart defaults (e.g. automatic `on_closing` listener that quits `window`)\n- Automates wiring of controls (e.g. `button` is automatically set as child of `window`)\n- Hides lower-level details (e.g. `LibUI.main` loop is started automatically when triggering `show` on `window`)\n- Nests controls according to their visual hierarchy\n- Requires the minimum amount of syntax needed to describe an app's GUI\n\nThe Glimmer GUI DSL follows these simple concepts in mapping from [LibUI](https://github.com/kojix2/LibUI) syntax:\n\n**Keyword(args)**: [LibUI](https://github.com/kojix2/LibUI) controls may be declared by lower-case underscored name (aka keyword from list of [supported keywords](#supported-keywords)) (e.g. `window` or `button`). Behind the scenes, they are represented by keyword methods that map to corresponding `LibUI.new_keyword` methods receiving args (e.g. `window('hello world', 300, 200, true)`).\n\n**Content Block** (Properties/Listeners/Controls): Any keyword may be optionally followed by a Ruby curly-brace multi-line content block containing properties (attributes), listeners, and/or nested controls.\n\nExample:\n\n```ruby\nwindow {\n  title 'hello world' # property\n  \n  on_closing do # listener (always has a do; end block to signify logic)\n    puts 'Bye'\n  end\n  \n  button('greet') { # nested control\n    on_clicked do\n      puts 'hello world'\n    end\n  }\n}\n```\n\nContent block optionally receives one arg representing the controll\n\nExample:\n\n```ruby\nbutton('greet') { |b|\n  on_clicked do\n    puts b.text\n  end\n}\n```\n\nIf there is ever a need to add more content to a control, you can re-open its content with the `control.content { ... }` method.\n\nExample:\n\n```ruby\nbox1 = vertical_box {\n  label('First Name')\n}\n# re-open content of box1 and add another control\nbox1.content {\n  entry {\n    text 'fill in your first name'\n  }\n}\n```\n\nContent Data-Binding also allows you to use [data-binding](#data-binding) with content blocks to generate content dynamically based on changes in a model attribute. The only difference in syntax in this case would be to wrap the content with an explicit `content(*binding_args) { ... }` block (like `content(model, attribute) { somecontrols }` ) that includes data-binding arguments for a model attribute.\n\nExample:\n\n```ruby\nform {\n  stretchy false\n  \n  content(@user, :customizable_attributes) {\n    # this content will be re-rendered whenever @user.customizable_attributes changes\n    @user.customizable_attributes.each do |attribute|\n      entry {\n        label attribute.to_s.split('_').map(\u0026:capitalize).join(' ')\n        text \u003c=\u003e [@user, attribute]\n      }\n    end\n  }\n}\n```\n\nThe form above will only display fields for a model's customizable attributes, so if they change, the form content will change too.\n\nIf you need to rebuild (re-render) content upon changes to multiple model attributes, you can use the `computed_by` option.\n\nExample:\n\n```ruby\nform {\n  stretchy false\n  \n  content(@user, :address, computed_by: [:street, :city, :zipcode]) {\n    @user.address_attributes.each do |attribute|\n      entry {\n        label attribute.to_s.split('_').map(\u0026:capitalize).join(' ')\n        text \u003c=\u003e [@user, attribute]\n      }\n    end\n  }\n}\n```\n\nNow, the content block will get called when changes occur to any of `User` `address` ,`street`, `city`, or `zipcode`.\n\nIf you do not have a main attribute that is computed by other attributes, you can leave the main attribute out while using `computed_by`.\n\nExample:\n\n```ruby\nform {\n  stretchy false\n  \n  content(@user, computed_by: [:street, :city, :zipcode]) {\n    @user.address_attributes.each do |attribute|\n      entry {\n        label attribute.to_s.split('_').map(\u0026:capitalize).join(' ')\n        text \u003c=\u003e [@user, attribute]\n      }\n    end\n  }\n}\n```\n\nNow, the content block will get called (rerendered) when changes occur to any of `User` `street`, `city`, or `zipcode`.\n\nLearn more about Content Data-Binding at the [Dynamic Form](/docs/examples/GLIMMER-DSL-LIBUI-ADVANCED-EXAMPLES.md#dynamic-form) example.\n\n**Property**: Control properties may be declared inside keyword blocks with lower-case underscored name followed by property value args (e.g. `title \"hello world\"` inside `group`). Behind the scenes, properties correspond to `LibUI.control_set_property` methods.\n\n**Listener**: Control listeners may be declared inside keyword blocks with listener lower-case underscored name beginning with `on_` and receiving required block handler (always followed by a `do; end` style block to signify logic).\n\nExample:\n\n```ruby\nbutton('click') {\n  on_clicked do\n    puts 'clicked'\n  end\n}\n```\n\nOptionally, the listener block can receive an arg representing the control.\n\n```ruby\nbutton('click') {\n  on_clicked do |btn|\n    puts btn.text\n  end\n}\n```\n\nBehind the scenes, listeners correspond to `LibUI.control_on_event` methods.\n\n**Method**: Controls have methods that invoke certain operations on them. For example, `window` has a `#show` method that shows the window GUI. More methods are mentioned under [API](#api)\n\nExample of an app written in [LibUI](https://github.com/kojix2/LibUI)'s procedural imperative syntax:\n\n```ruby\nrequire 'libui'\n\nUI = LibUI\n\nUI.init\n\nmain_window = UI.new_window('hello world', 300, 200, 1)\n\nbutton = UI.new_button('Button')\n\nUI.button_on_clicked(button) do\n  UI.msg_box(main_window, 'Information', 'You clicked the button')\nend\n\nUI.window_on_closing(main_window) do\n  puts 'Bye Bye'\n  UI.control_destroy(main_window)\n  UI.quit\n  0\nend\n\nUI.window_set_child(main_window, button)\nUI.control_show(main_window)\n\nUI.main\nUI.quit\n```\n\nExample of the same app written in [Glimmer](https://github.com/AndyObtiva/glimmer) object-oriented declarative hierarchical syntax:\n\n```ruby\nrequire 'glimmer-dsl-libui'\n\ninclude Glimmer\n\nwindow('hello world', 300, 200) {\n  button('Button') {\n    on_clicked do\n      msg_box('Information', 'You clicked the button')\n    end\n  }\n  \n  on_closing do\n    puts 'Bye Bye'\n  end\n}.show\n```\n\nMake sure that you follow the [Glimmer Style Guide](#glimmer-style-guide) when writing any Glimmer GUI DSL code.\n\n## API\n\nAny 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`).\n\nExample (you may copy/paste in [`girb`](#girb-glimmer-irb)):\n\n```ruby\nw = window('hello world')\nputs w.title # =\u003e hello world\nw.title = 'howdy'\nputs w.title # =\u003e howdy\nw.set_title 'aloha'\nputs w.title # =\u003e aloha\n```\n\nControls are wrapped as Ruby proxy objects, having a `#libui` method to obtain the wrapped [LibUI](https://github.com/kojix2/LibUI) 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. That said, you can invoke any [LibUI operation](#libui-operations) on the Glimmer proxy object directly and it gets proxied automatically to the wrapped Fiddle pointer object (e.g. `window_proxy.title` gets proxied to `LibUI.window_title(window_proxy.libui).to_s` automatically), so you rarely have to refer to the wrapped `#libui` Fiddle pointer object directly.\n\nExample (you may copy/paste in [`girb`](#girb-glimmer-irb)):\n\n```ruby\nw = window('hello world') # =\u003e #\u003cGlimmer::LibUI::WindowProxy:0x00007fde4ea39fb0\nw.libui # =\u003e #\u003cFiddle::Pointer:0x00007fde53997980 ptr=0x00007fde51352a60 size=0 free=0x0000000000000000\u003e\nw.title == LibUI.window_title(w.libui).to_s # =\u003e true\n```\n\n### Supported Keywords\n\nThese are all the supported keywords. Note that some keywords do not represent controls. For example, some keywords produce objects that are used as the property values of controls (e.g. `image` can be used as a control under `area` or alternatively build objects to use in `cell_rows` for a `table` with an `image_column`)\n\nKeyword(Args) | Properties | Listeners\n------------- | ---------- | ---------\n`about_menu_item` | None | `on_clicked`\n`area` | `auto_draw_enabled` | `on_draw(area_draw_params)`, `on_mouse_event(area_mouse_event)`, `on_mouse_moved(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)`\n`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\n`background_color_column` | None | None\n`bezier(x = nil as Numeric, y = nil as Numeric, c1_x as Numeric, c1_y as Numeric, c2_x as Numeric, c2_y as Numeric, end_x as Numeric, end_y as Numeric)` | `x` (`Numeric`), `y` (`Numeric`), `c1_x` (`Numeric`), `c1_y` (`Numeric`), `c2_x` (`Numeric`), `c2_y` (`Numeric`), `end_x` (`Numeric`), `end_y` (`Numeric`) | None\n`button(text as String)` | `text` (`String`) | `on_clicked`\n`button_column(name as String)` | `enabled` (Boolean) | None\n`checkbox(text as String)` | `checked` (Boolean), `text` (`String`) | `on_toggled`\n`checkbox_column(name as String)` | `editable` (Boolean) | None\n`checkbox_text_column(name as String)` | `editable` (Boolean), `editable_checkbox` (Boolean), `editable_text` (Boolean) | None\n`checkbox_text_color_column(name as String)` | `editable` (Boolean), `editable_checkbox` (Boolean), `editable_text` (Boolean) | None\n`check_menu_item(text as String)` | `checked` (Boolean) | `on_clicked`\n`code_area` | `language` (String) (default: `'ruby'`), `theme` (String) (default: `'glimmer'`), `code` (String) | None\n`combobox` | `items` (`Array` of `String`), `selected` (`Integer`), `selected_item` (`String`) | `on_selected`\n`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`\n`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`\n`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`\n`editable_combobox` | `items` (`Array` of `String`), `text` (`String`) | `on_changed`\n`entry` | `read_only` (Boolean), `text` (`String`) | `on_changed`\n`figure(x=nil as Numeric, y=nil as Numeric)` | `x` (`Numeric`), `y` (`Numeric`), `closed` (Boolean) | None\n`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`\n`form` | `padded` (Boolean) | None\n`grid` | `padded` (Boolean) | None\n`group(text as String)` | `margined` (Boolean), `title` (`String`) | None\n`horizontal_box` | `padded` (Boolean) | None\n`horizontal_separator` | None | None\n`image(file as String = nil, width as Numeric = nil, height as Numeric = nil)` | `file` (`String` path or URL), `width`, `height` | None\n`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\n`image_column(name as String)` | None | None\n`image_text_column(name as String)` | None | None\n`image_text_color_column(name as String)` | None | None\n`label(text as String)` | `text` (`String`) | None\n`line(x as Numeric, y as Numeric, end_x = nil as Numeric, end_y = nil as Numeric)` | `x` (`Numeric`), `y` (`Numeric`), `end_x` (`Numeric`), `end_y` (`Numeric`) | None\n`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\n`menu(text as String)` | None | None\n`menu_item(text as String)` | None | `on_clicked`\n`message_box` (alias for `msg_box`; see for arguments) | None | None\n`message_box_error` (alias for `msg_box_error`; see for arguments) | None | None\n`multiline_entry` | `read_only` (Boolean), `text` (`String`) | `on_changed`\n`msg_box(window = main_window as Glimmer::LibUI::WindowProxy, title as String, description as String)` | None | None\n`msg_box_error(window = main_window as Glimmer::LibUI::WindowProxy, title as String, description as String)` | None | None\n`non_wrapping_multiline_entry` | `read_only` (Boolean), `text` (`String`) | `on_changed`\n`observe(model, property = nil)` | None | None\n`password_entry` | `read_only` (Boolean), `text` (`String`) | `on_changed`\n`path(draw_fill_mode = :winding)` | `fill` (`Hash` of `:r` as `0`-`255`, `:g` as `0`-`255`, `:b` as `0`-`255`, `:a` as `0.0`-`1.0`, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color), `stroke` (`Hash` of `:r` as `0`-`255`, `:g` as `0`-`255`, `:b` as `0`-`255`, `:a` as `0.0`-`1.0`, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color), `:cap` as (`:round`, `:square`, `:flat`), `:join` as (`:miter`, `:round`, `:bevel`), `:thickness` as `Numeric`, `:miter_limit` as `Numeric`, `:dashes` as `Array` of `Numeric` ) | None\n`polygon(point_array as Array of Arrays of Numeric or Array of Numeric)` | `point_array` (`Array of Arrays of Numeric or Array of Numeric`) | None\n`polyline(point_array as Array of Arrays of Numeric or Array of Numeric)` | `point_array` (`Array of Arrays of Numeric or Array of Numeric`) | None\n`polybezier(point_array as Array of Arrays of Numeric or Array of Numeric)` | `point_array` (`Array of Arrays of Numeric or Array of Numeric`) | None\n`preferences_menu_item` | None | `on_clicked`\n`progress_bar` | `value` (`Numeric`) | None\n`progress_bar_column(name as String)` | None | None\n`quit_menu_item` | None | `on_clicked`\n`radio_buttons` | `selected` (`Integer`) | `on_selected`\n`rectangle(x as Numeric, y as Numeric, width as Numeric, height as Numeric)` |  `x` (`Numeric`), `y` (`Numeric`), `width` (`Numeric`), `height` (`Numeric`) | None\n`refined_table` | `model_array` (`Array`), `table_columns` (`Hash`), `table_editable` (Boolean), `per_page` (`Integer`), `page` (`Integer`), `visible_page_count` (Boolean), `filter_query` (`String`), `filter` (Lambda) | (EARLY ALPHA UNSTABLE API / CHECK SOURCE CODE FOR DETAILS)\n`scrolling_area(width = main_window.width, height = main_window.height)` | `auto_draw_enabled` (Boolean), `size` (`Array` of `width` (`Numeric`) and `height` (`Numeric`)), `width` (`Numeric`), `height` (`Numeric`) | `on_draw(area_draw_params)`, `on_mouse_event(area_mouse_event)`, `on_mouse_down(area_mouse_event)`, `on_mouse_up(area_mouse_event)`, `on_mouse_drag_started(area_mouse_event)`, `on_mouse_dragged(area_mouse_event)`, `on_mouse_dropped(area_mouse_event)`, `on_mouse_entered`, `on_mouse_exited`, `on_key_event(area_key_event)`, `on_key_down(area_key_event)`, `on_key_up(area_key_event)`\n`search_entry` | `read_only` (Boolean), `text` (`String`) | `on_changed`\n`separator_menu_item` | None | None\n`slider(min as Numeric, max as Numeric)` | `value` (`Numeric`) | `on_changed`\n`spinbox(min as Numeric, max as Numeric)` | `value` (`Numeric`) | `on_changed`\n`square(x as Numeric, y as Numeric, length as Numeric)` | `x` (`Numeric`), `y` (`Numeric`), `length` (`Numeric`) | None\n`string(string = '')` | `font`, `color` (`Hash` of `:r` as `0`-`255`, `:g` as `0`-`255`, `:b` as `0`-`255`, `:a` as `0.0`-`1.0`, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color), `background` (`Hash` of `:r` as `0`-`255`, `:g` as `0`-`255`, `:b` as `0`-`255`, `:a` as `0.0`-`1.0`, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color), `underline`, `underline_color` (`Hash` of `:r` as `0`-`255`, `:g` as `0`-`255`, `:b` as `0`-`255`, `:a` as `0.0`-`1.0`, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color), `open_type_features`, `string` (`String`) | None\n`tab` | `margined` (Boolean), `num_pages` (`Integer`) | None\n`tab_item(name as String)` | `index` [read-only] (`Integer`), `margined` (Boolean), `name` [read-only] (`String`) | None\n`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, `selection_mode` (`:zero_or_many` , `:none` , `:zero_or_one` , or `:one`), `selection` (`Integer` for row index or `Array` of multiple row indexes), `header_visible` (Boolean) | `on_changed {|row, type, row_data| ...}`, `on_edited {|row, row_data| ...}`, `on_row_clicked {|table, row| }`, `on_row_double_clicked {|table, row| }`, and `on_selection_changed {|table, selection, added_selection, removed_selection| }`\n`text(x = 0 as Numeric, y = 0 as Numeric, width = area_width as Numeric)` | `align`, `default_font` | None\n`text_column(name as String)` | `editable` (Boolean), `sort_indicator` (`:ascending` [alias: `:asc`, `:a`], `:descending` [alias: `:desc`, `:d`], or `nil`) | `on_clicked {|tc, column_index| }`\n`text_color_column(name as String)` | `editable` (Boolean) | None\n`time_picker` | `time` (`Hash` of keys: `sec` as `Integer`, `min` as `Integer`, `hour` as `Integer`) | `on_changed`\n`vertical_box` | `padded` (Boolean) | None\n`vertical_separator` | None | None\n`window(title as String, width as Integer, height as Integer, has_menubar as Boolean)` | `borderless` (Boolean), `content_size` (width `Numeric`, height `Numeric`), `width` (`Numeric`), `height` (`Numeric`), `focused` (Boolean), `fullscreen` (Boolean), `margined` (Boolean), `title` (`String`), `resizable` (Boolean) | `on_closing`, `on_content_size_changed`, `on_focus_changed`, `on_destroy`\n\n### Common Control Properties\n- `enabled` (Boolean)\n- `libui` (`Fiddle::Pointer`): returns wrapped [LibUI](https://github.com/kojix2/LibUI) object\n- `parent_proxy` (`Glimmer::LibUI::ControlProxy` or subclass)\n- `parent` (`Fiddle::Pointer`)\n- `toplevel` [read-only] (Boolean)\n- `visible` (Boolean)\n- `stretchy` [dsl-only] (Boolean) [default=`true`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `horizontal_box`, `vertical_box`, or `form`\n- `left` [dsl-only] (`Integer`) [default=`0`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `grid`\n- `top` [dsl-only] (`Integer`) [default=`0`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `grid`\n- `xspan` [dsl-only] (`Integer`) [default=`1`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `grid`\n- `yspan` [dsl-only] (`Integer`) [default=`1`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `grid`\n- `hexpand` [dsl-only] (Boolean) [default=`false`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `grid`\n- `halign` [dsl-only] (`:fill`, `:start`, `:center`, or `:end`) [default=`:fill`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `grid`\n- `vexpand` [dsl-only] (Boolean) [default=`false`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `grid`\n- `valign` [dsl-only] (`:fill`, `:start`, `:center`, or `:end`) [default=`:fill`]: available in [Glimmer GUI DSL](#glimmer-gui-dsl-concepts) when nested under `grid`\n\n### Common Control Operations\n- `destroy` (note that for closing a `window`, in addition to calling `somewindow.destroy`, you also have to call `::LibUI.quit`)\n- `disable`\n- `enable`\n- `hide`\n- `show`\n\n### LibUI Operations\n\nAll operations that could normally be called on `LibUI` can also be called on `Glimmer::LibUI`, but some have enhancements as detailed below.\n\n- `Glimmer::LibUI::queue_main(\u0026block)`: queues an operation to be run on the main event loop at the earliest opportunity possible. When writing multi-threaded code, it is required to wrap all code interacting with GUI objects (like `window` or `button`) from another `Thread` with `Glimmer::LibUI::queue_main { ... }`. See [Glimmer Meta-Example](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/examples/meta_example.rb) for an example of using `Glimmer::LibUI::queue_main { ... }` inside another `Thread`.\n- `Glimmer::LibUI::timer(time_in_seconds=0.1, repeat: true, \u0026block)`: 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.\n\nThere are additional useful `Glimmer::LibUI` operations that are not found in `LibUI`, which mostly help if you would like to do advanced lower level [LibUI](https://github.com/kojix2/LibUI) programming:\n- `Glimmer::LibUI::integer_to_boolean(int, allow_nil: true)`\n- `Glimmer::LibUI::boolean_to_integer(int, allow_nil: true)`\n- `Glimmer::LibUI::degrees_to_radians(degrees)`\n- `Glimmer::LibUI::interpret_color(value)`: interprets a color in any form like `String`, `Symbol`, or hex into an rgb `Hash` (including `0x1f3b5d`, `'0x1f3b5d'`, `'#1f3b5d'`, and 3-char hex-shorthand variations)\n- `Glimmer::LibUI::hex_to_rgb(value)`: converts a hex color to an rgb `Hash` (including `0x1f3b5d`, `'0x1f3b5d'`, `'#1f3b5d'`, and 3-char hex-shorthand variations)\n- `Glimmer::LibUI::enum_names`: provides all possible enum names to use with `Glimmer::LibUI::enum_symbols(enum_name)`\n- `Glimmer::LibUI::enum_symbols(enum_name)`: returns all possible values for an enum. `enum_name` can be:\n  - `:draw_brush_type`: `[:solid, :linear_gradient, :radial_gradient, :image]`\n  - `:draw_line_cap`: `[:flat, :round, :square]`\n  - `:draw_line_join`: `[:miter, :round, :bevel]`\n  - `:draw_fill_mode`: `[:winding, :alternate]`\n  - `:attribute_type`: attributes for attributed `string`s: `[:family, :size, weight, :italic, :stretch, :color, :background, :underline, :underline_color, :features]`\n  - `:text_weight`: `[:minimum, :thin, :ultra_light, :light, :book, :normal, :medium, :semi_bold, :bold, :ultra_bold, :heavy, :ultra_heavy, :maximum]`\n  - `:text_italic`: `[:normal, :oblique, :italic]`\n  - `:text_stretch`: `[:ultra_condensed, :extra_condensed, :condensed, :semi_condensed, :normal, :semi_expanded, :expanded, :extra_expanded, :ultra_expanded]`\n  - `:underline`: `[:none, :single, :double, :suggestion, :color_custom, :color_spelling, :color_grammar, :color_auxiliary]`\n  - `:underline_color`: `[:custom, :spelling, :grammar, :auxiliary]`\n  - `:draw_text_align`: `[:left, :center, :right]`\n  - `:modifier`: `[:ctrl, :alt, :shift, :super]`\n  - `:ext_key`: `[:escape, :insert, :delete, :home, :end, :page_up, :page_down, :up, :down, :left, :right, :f1, :f2, :f3, :f4, :f5, :f6, :f7, :f8, :f9, :f10, :f11, :f12, :n0, :n1, :n2, :n3, :n4, :n5, :n6, :n7, :n8, :n9, :n_dot, :n_enter, :n_add, :n_subtract, :n_multiply, :n_divide]`\n  - `:at`: for inserting `grid` controls: `[:leading, :top, :trailing, :bottom]`\n  - `:align`: `[:fill, :start, :center, :end]`\n  - `:table_value_type`: `[:string, :image, :int, :color]`\n  - `:table_model_column`: `[:never_editable, :always_editable]`\n- `Glimmer::LibUI::enum_symbol_to_value(enum_name, enum_symbol, default_symbol: nil, default_index: 0)`\n- `Glimmer::LibUI::enum_value_to_symbol(enum_name, enum_value)`\n- `Glimmer::LibUI::x11_colors`: returns all [X11 colors](https://en.wikipedia.org/wiki/X11_color_names): `[:alice_blue, :antique_white, :aqua, :aquamarine, :azure, :beige, :bisque, :rebecca_purple, :becca_purple, :blanched_almond, :blue, :blue_violet, :brown, :burly_wood, :burlywood, :cadet_blue, :carnation, :cayenne, :chartreuse, :chocolate, :coral, :cornflower_blue, :cornsilk, :crimson, :cyan, :dark_blue, :dark_cyan, :dark_golden_rod, :dark_goldenrod, :dark_gray, :dark_grey, :dark_green, :dark_khaki, :dark_magenta, :dark_olive_green, :darkolive_green, :dark_orange, :dark_orchid, :dark_red, :dark_salmon, :darksalmon, :dark_sea_green, :dark_slate_blue, :dark_slate_gray, :dark_slate_grey, :dark_turquoise, :dark_violet, :darkorange, :deep_pink, :deep_sky_blue, :dim_gray, :dim_grey, :dodger_blue, :feldspar, :fire_brick, :firebrick, :floral_white, :forest_green, :fuchsia, :gainsboro, :ghost_white, :gold, :golden_rod, :goldenrod, :gray, :grey, :gray10, :grey10, :gray20, :grey20, :gray30, :grey30, :gray40, :grey40, :gray50, :grey50, :gray60, :grey60, :gray70, :grey70, :gray80, :grey80, :gray90, :grey90, :green, :green_yellow, :honey_dew, :honeydew, :hot_pink, :indian_red, :indigo, :ivory, :khaki, :lavender, :lavender_blush, :lawn_green, :lemon_chiffon, :light_blue, :light_coral, :light_cyan, :light_golden_rod_yellow, :light_goldenrod_yellow, :light_gray, :light_grey, :light_green, :light_pink, :light_salmon, :lightsalmon, :light_sea_green, :light_sky_blue, :light_slate_blue, :light_slate_gray, :light_slate_grey, :light_steel_blue, :lightsteel_blue, :light_yellow, :lime, :lime_green, :linen, :magenta, :maroon, :medium_aqua_marine, :medium_aquamarine, :medium_blue, :medium_orchid, :medium_purple, :medium_sea_green, :medium_slate_blue, :medium_spring_green, :medium_turquoise, :medium_violet_red, :midnight_blue, :mint_cream, :misty_rose, :moccasin, :navajo_white, :navy, :old_lace, :olive, :olive_drab, :olivedrab, :orange, :orange_red, :orchid, :pale_golden_rod, :pale_goldenrod, :pale_green, :pale_turquoise, :pale_violet_red, :papaya_whip, :peach_puff, :peachpuff, :peru, :pink, :plum, :powder_blue, :purple, :red, :rosy_brown, :royal_blue, :saddle_brown, :salmon, :sandy_brown, :sea_green, :sea_shell, :seashell, :sienna, :silver, :sky_blue, :slate_blue, :slate_gray, :slate_grey, :snow, :spring_green, :steel_blue, :tan, :teal, :thistle, :tomato, :turquoise, :violet, :violet_red, :wheat, :white_smoke, :yellow, :yellow_green, :metallic, :white, :black, :gray_scale, :grey_scale]`\n\n### Extra Dialogs\n\n- `open_file(window as Glimmer::LibUI::WindowProxy = ControlProxy::main_window_proxy)`: returns selected file (`String`) or `nil` if cancelled\n- `open_folder(window as Glimmer::LibUI::WindowProxy = ControlProxy::main_window_proxy)`: returns selected folder/directory (`String`) or `nil` if cancelled\n- `save_file(window as Glimmer::LibUI::WindowProxy = ControlProxy::main_window_proxy)`: returns selected file (`String`) or `nil` if cancelled\n\n### Extra Operations\n\n- `ControlProxy::control_proxies`: returns all instantiated control proxies in the application\n- `ControlProxy::menu_proxies`: returns all instantiated `menu` proxies in the application\n- `ControlProxy::image_proxies`: returns all instantiated `image` proxies in the application\n- `ControlProxy::main_window_proxy`: returns the first window proxy instantiated in the application\n- `ControlProxy#window_proxy`: returns the window proxy parent for a control\n- `ControlProxy#content {...}`: re-opens control's content to add more nested controls or properties\n\n### Table API\n\nThe `table` control must first declare its columns via one of these column keywords (mentioned in [Supported Keywords](#supported-keywords)):\n  - `background_color_column`: expects color cell values\n  - `button_column`: expects `String` cell values and a nested `on_clicked` listener that gets triggerd when a button is clicked\n  - `checkbox_column`: expects Boolean cell values\n  - `checkbox_text_column`: expects dual-element `Array` of Boolean and `String` cell values\n  - `checkbox_text_color_column`: expects triple-element `Array` of Boolean, `String`, and color cell values\n  - `image_column`: expects `image` cell values (produced by `image` and `image_part` keywords as per [Supported Keywords](#supported-keywords))\n  - `image_text_column`: expects dual-element `Array` of `image` and `String` cell values\n  - `image_text_color_column`: expects triple-element `Array` of `image`, `String`, and color cell values\n  - `text_column`: expects `String` cell values\n  - `text_color_column`: expects dual-element `Array` of `String` and color cell values\n  - `progress_bar_column`: expects `Integer` cell values\n  \nAfterwards, 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.\n\nNote 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:\n- 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\n- Inserting cell rows: Calling `Array#\u003c\u003c`, `Array#push`, `Array#prepend`, or any insertion/addition `Array` method automatically inserts rows in actual `table` control\n- Changing cell rows: Calling `Array#[]=`, `Array#map!`, or any update `Array` method automatically updates rows in actual `table` control\n\nMore details about table data-binding can be found in [examples/basic_table.rb](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/docs/examples/GLIMMER-DSL-LIBUI-BASIC-EXAMPLES.md#basic-table) or other `table` [basic examples](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/docs/examples/GLIMMER-DSL-LIBUI-BASIC-EXAMPLES.md) and [advanced examples](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/docs/examples/GLIMMER-DSL-LIBUI-ADVANCED-EXAMPLES.md).\n\nThe `table` control supports table selection and table sorting automatically as smart defaults, which can also be configured if needed as per the options below.\n\nThere are other properties that `table` supports:\n- `selection_mode` (`Symbol`) [default: `:zero_or_one`]: sets selection mode to `:one`, `:zero_or_one`, `:zero_or_many`, or `:none`\n- `selection` (`Integer` or `Array` of `Integer`s): a single `Integer` row index for `:one` and `:zero_or_one` selection modes, or an `Array` of `Integer` row indexes if selection mode is `:zero_or_many`\n- `header_visible` (Boolean): shows or hides column headers\n- `sortable` (Boolean) [default: `true`]: enables automatic table sorting support\n- `on_changed {|row, type, row_data| ...}`: triggered upon a change to a row that happens outside the table in the model\n- `on_edited {|row, row_data| ...}`: triggered upon a change to a row that happens in the table GUI through editing.\n- `on_row_clicked {|table, row| }`: triggered upon clicking a table row\n- `on_row_double_clicked {|table, row| }`: triggered upon double-clicking a table row\n- `on_selection_changed {|table, selection, added_selection, removed_selection| }`: triggered upon selecting a table row\n\nTo handle `table` sorting manually, the following can be set inside a table column:\n- `sort_indicator` (`Symbol`): sets sort indicator to ascending or descending with the value being `:ascending`, `:descending`, `:asc`, `:desc`, `:a`, or `:d`\n- `on_clicked` (`Proc`): this listener is triggered when a table column is clicked\n\nMore details about table selection and table sorting can be found in [examples/basic_table_selection.rb](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/docs/examples/GLIMMER-DSL-LIBUI-BASIC-EXAMPLES.md#basic-table-selection).\n\n([explicit data-binding](#data-binding) supports everything available with implicit data-binding too)\n\nExample (you may copy/paste in [`girb`](#girb-glimmer-irb)):\n\n```ruby\nrequire 'glimmer-dsl-libui'\n\ninclude Glimmer\n\ndata = [\n  ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],\n  ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],\n  ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],\n  ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],\n  ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],\n]\n\nwindow('Contacts', 600, 600) {\n  margined true\n  \n  vertical_box {\n    form {\n      stretchy false\n      \n      @name_entry = entry {\n        label 'Name'\n      }\n      \n      @email_entry = entry {\n        label 'Email'\n      }\n      \n      @phone_entry = entry {\n        label 'Phone'\n      }\n      \n      @city_entry = entry {\n        label 'City'\n      }\n      \n      @state_entry = entry {\n        label 'State'\n      }\n    }\n    \n    button('Save Contact') {\n      stretchy false\n      \n      on_clicked do\n        new_row = [@name_entry.text, @email_entry.text, @phone_entry.text, @city_entry.text, @state_entry.text]\n        if new_row.map(\u0026:to_s).include?('')\n          msg_box_error('Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')\n        else\n          data \u003c\u003c new_row # automatically inserts a row into the table due to implicit data-binding\n          @unfiltered_data = data.dup\n          @name_entry.text = ''\n          @email_entry.text = ''\n          @phone_entry.text = ''\n          @city_entry.text = ''\n          @state_entry.text = ''\n        end\n      end\n    }\n    \n    search_entry { |se|\n      stretchy false\n      \n      on_changed do\n        filter_value = se.text\n        @unfiltered_data ||= data.dup\n        # Unfilter first to remove any previous filters\n        data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding\n        # Now, apply filter if entered\n        unless filter_value.empty?\n          data.filter! do |row_data| # affects table indirectly through implicit data-binding\n            row_data.any? do |cell|\n              cell.to_s.downcase.include?(filter_value.downcase)\n            end\n          end\n        end\n      end\n    }\n    \n    table {\n      text_column('Name')\n      text_column('Email')\n      text_column('Phone')\n      text_column('City')\n      text_column('State')\n\n      editable true\n      cell_rows data # implicit data-binding to raw data Array of Arrays\n      \n      on_changed do |row, type, row_data|\n        puts \"Row #{row} #{type}: #{row_data}\"\n        $stdout.flush # for Windows\n      end\n      \n      on_edited do |row, row_data| # only fires on direct table editing\n        puts \"Row #{row} edited: #{row_data}\"\n        $stdout.flush # for Windows\n      end\n    }\n  }\n}.show\n```\n\nMac | Windows | Linux\n----|---------|------\n![glimmer-dsl-libui-mac-form-table.png](images/glimmer-dsl-libui-mac-form-table.png) | ![glimmer-dsl-libui-windows-form-table.png](images/glimmer-dsl-libui-windows-form-table.png) | ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png)\n\nLearn more by checking out [examples](#examples).\n\n#### Refined Table\n\n[EARLY ALPHA FEATURE]\n\n`refined_table` is a [custom control](#custom-components) provided exclusively by [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui)\nthat includes filtering and pagination support out of the box and can handle very large amounts of data (e.g. 50,000 rows).\n\nIt is currently an early alpha feature, so please test-drive and report issues if you encounter any.\nAnd, please keep in mind that the API might undergo big changes.\n\nOptions (passed as kwargs hash):\n\n- `model_array` (`Array`): array of models for which attributes map to table columns\n- `filter_query` (`String`): query term to filter table by; AND-matching multiple words against all columns (e.g. `John Illinois` returns John Doe from Illinois, USA), a double-quoted exact term match against all columns (e.g. `\"Urbana Champaign\"` returns only results from the town of \"Urbana Champaign\"), or a match against specific columns (e.g. `first_name:John`, `\"first name\":John`, `first_name:\"John Doe\"`, or `\"first name\":\"john doe\"`). You may mix and match different types of filter queries. All matches are case-insensitive. To customize filtering differently, you may set the `filter` option explained below.\n- `table_columns` (`Hash`): this maps column types to symbols (e.g. `text_column` becomes `:text`) with hash options per column\n- `table_editable` (Boolean) [default: `false`]: this indicates if all table columns are editable or not.\n- `per_page` (`Integer`) [default: `10`]: number of rows per page\n- `page` (`Integer`) [default: `1`]: initial page displayed\n- `pagination` (Boolean) [default: `true`]: pagination is enabled (disabling still keeps the filter field)\n- `visible_page_count` (Boolean) [default: `false`]: shows \"of PAGE_COUNT pages\" after page `entry` field\n- `filter` (Lambda) [default: `Glimmer::LibUI::CustomControl::RefinedTable::FILTER_DEFAULT`]: enables setting custom filter that accepts `row_hash` (mapping table column names to row values) and `query` string as arguments, and it is supposed to return `true` or `false` for whether to show a row or filter it out.\n\nAPI:\n\n- `refined_model_array` (`Array`): `model_array` with filtering and pagination applied (useful to grab a table row model by index).\n- `filtered_model_array` (`Array`): `model_array` with filtering applied, but without pagination\n- `table_proxy`: control proxy object for the `table` contained in the `refined_table` [custom control](#custom-components)\n\nIf the initial `model_array` has no more than a single page of data, then pagination buttons are hidden (but, the filter field remains).\n\nExample code:\n\n```ruby\nrefined_table(\n  model_array: contacts,\n  table_columns: {\n    'Name'  =\u003e {button: {on_clicked: -\u003e(row) {puts row}}},\n    'Colored Email' =\u003e :text_color,\n    'Phone' =\u003e {text: {editable: true}},\n    'City'  =\u003e :text,\n    'State' =\u003e :text,\n  },\n  table_editable: true, # default value is false\n  per_page: 20, # row count per page\n  # page: 1, # initial page is 1\n  # visible_page_count: true, # page count can be shown if preferred\n)\n```\n\nMac | Windows | Linux\n----|---------|------\n![glimmer-dsl-libui-mac-paginated-refined-table.png](images/glimmer-dsl-libui-mac-paginated-refined-table.png)| ![glimmer-dsl-libui-windows-paginated-refined-table.png](images/glimmer-dsl-libui-windows-paginated-refined-table.png)| ![glimmer-dsl-libui-linux-paginated-refined-table.png](images/glimmer-dsl-libui-linux-paginated-refined-table.png)\n\nLearn more in the [Paginated Refined Table](/docs/examples/GLIMMER-DSL-LIBUI-ADVANCED-EXAMPLES.md#paginated-refined-table) example.\n\n### Area API\n\nThe `area` control is a canvas-like control for drawing paths that can be used in one of two ways:\n- Retained Mode (declaratively via stable shape structures): useful for stable paths that will not change often 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 relatively few stable paths (mostly for decorative reasons).\n- Immediate Mode (semi-declaratively via `on_draw` listener dynamic shapes): useful for more dynamic paths that will definitely change very often. Open an `on_draw` listener block that receives an [`area_draw_params`](#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.\n\nNote 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.\n\nAlso, note that Canvas graphics performance is a bit slow today due to the Ruby LibUI binding making Canvas drawing calls with FFI. There is currently work under way to re-implement the Canvas drawing calls with C Native Extensions, which should speed up performance by 10x-100x once fully implemented. Still, if the `area` control is needed to paint si","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAndyObtiva%2Fglimmer-dsl-libui","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FAndyObtiva%2Fglimmer-dsl-libui","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAndyObtiva%2Fglimmer-dsl-libui/lists"}