glimmer-dsl-libui 0.2.8 → 0.2.12

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
- # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.2.8
1
+ # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.2.12
2
2
  ## Prerequisite-Free Ruby Desktop Development GUI Library
3
3
  [![Gem Version](https://badge.fury.io/rb/glimmer-dsl-libui.svg)](http://badge.fury.io/rb/glimmer-dsl-libui)
4
- [![Maintainability](https://api.codeclimate.com/v1/badges/ce2853efdbecf6ebdc73/maintainability)](https://codeclimate.com/github/AndyObtiva/glimmer-dsl-libui/maintainability)
5
4
  [![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
6
5
 
7
6
  [Glimmer](https://github.com/AndyObtiva/glimmer) DSL for [LibUI](https://github.com/kojix2/LibUI) is a prerequisite-free Ruby desktop development GUI library. No need to pre-install any prerequisites. Just install the gem and have platform-independent native GUI that just works!
@@ -10,14 +9,16 @@
10
9
 
11
10
  The main trade-off in using [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) as opposed to [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) or [Glimmer DSL for Tk](https://github.com/AndyObtiva/glimmer-dsl-tk) is the fact that [SWT](https://www.eclipse.org/swt/) and [Tk](https://www.tcl.tk/) are more mature than mid-alpha [libui](https://github.com/andlabs/libui) as GUI toolkits. Still, if there is only a need to build a small simple application, [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) could be a good convenient choice due to having zero prerequisites beyond the dependencies included in the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui). Also, just like [Glimmer DSL for Tk](https://github.com/AndyObtiva/glimmer-dsl-tk), its apps start instantly and have a small memory footprint. [LibUI](https://github.com/kojix2/LibUI) is a promising new GUI toolkit that might prove quite worthy in the future.
12
11
 
12
+ **(Note: although LibUI works on Windows, this project has not been tested on Windows yet. It has only been verified on Mac x64 and Linux x64. Issue reporting for Windows is appreciated in the meantime.)**
13
+
13
14
  [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:
14
15
  - Declarative DSL syntax that visually maps to the GUI control hierarchy
15
16
  - Convention over configuration via smart defaults and automation of low-level details
16
17
  - Requiring the least amount of syntax possible to build GUI
17
- - Bidirectional Data-Binding to declaratively wire and automatically synchronize GUI with Business Models
18
18
  - Custom Control support
19
- - Scaffolding for new custom controls, apps, and gems
20
- - Native-Executable packaging on Mac, Windows, and Linux.
19
+ - [Far Future Plan] Bidirectional Data-Binding to declaratively wire and automatically synchronize GUI with Business Models
20
+ - [Far Future Plan] Scaffolding for new custom controls, apps, and gems
21
+ - [Far Future Plan] Native-Executable packaging on Mac, Windows, and Linux.
21
22
 
22
23
  Hello, World!
23
24
 
@@ -61,7 +62,7 @@ window('Task Progress', 300, 200) {
61
62
 
62
63
  on_clicked do
63
64
  data.each_with_index do |row_data, row|
64
- data[row] = [row_data[0], 100] # automatically updates table due to implicit data-binding
65
+ data[row][1] = 100 # automatically updates table due to implicit data-binding
65
66
  end
66
67
  end
67
68
  }
@@ -84,14 +85,15 @@ window('Area Gallery', 400, 400) {
84
85
  path { # declarative stable path
85
86
  square(0, 0, 100)
86
87
  square(100, 100, 400)
87
-
88
+
88
89
  fill r: 102, g: 102, b: 204
89
90
  }
90
91
  path { # declarative stable path
91
92
  rectangle(0, 100, 100, 400)
92
93
  rectangle(100, 0, 400, 100)
93
-
94
- fill r: 204, g: 102, b: 204
94
+
95
+ # linear gradient (has x0, y0, x1, y1, and stops)
96
+ 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}]
95
97
  }
96
98
  path { # declarative stable path
97
99
  figure(100, 100) {
@@ -117,17 +119,24 @@ window('Area Gallery', 400, 400) {
117
119
  fill r: 202, g: 102, b: 204, a: 0.5
118
120
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
119
121
  }
122
+ path { # declarative stable path
123
+ arc(400, 220, 180, 90, 90, false)
124
+
125
+ # radial gradient (has an outer_radius in addition to x0, y0, x1, y1, and stops)
126
+ 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}]
127
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
128
+ }
120
129
  path { # declarative stable path
121
130
  circle(200, 200, 90)
122
131
 
123
132
  fill r: 202, g: 102, b: 204, a: 0.5
124
133
  stroke r: 0, g: 0, b: 0, thickness: 2
125
134
  }
126
- path { # declarative stable path
127
- arc(400, 220, 180, 90, 90, false)
128
-
129
- fill r: 204, g: 102, b: 204, a: 0.5
130
- stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
135
+ text(160, 40, 100) { # x, y, width
136
+ string('Area Gallery') {
137
+ font family: 'Times', size: 14
138
+ color :black
139
+ }
131
140
  }
132
141
 
133
142
  on_mouse_event do |area_mouse_event|
@@ -186,7 +195,7 @@ window('Area Gallery', 400, 400) {
186
195
 
187
196
  [Check Out Many More Examples Over Here!](#examples)
188
197
 
189
- NOTE: [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) is in early alpha mode (only supports included [examples](#examples)). Please help make better by contributing, adopting for small or low risk projects, and providing feedback. It is still an early alpha, so the more feedback and issues you report the better.
198
+ NOTE: [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) is feature-complete and in beta mode (though the C [libui](https://github.com/andlabs/libui) is still mid-alpha). 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.
190
199
 
191
200
  Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interested in:
192
201
  - [glimmer-dsl-swt](https://github.com/AndyObtiva/glimmer-dsl-swt): Glimmer DSL for SWT (JRuby Desktop Development GUI Framework)
@@ -197,7 +206,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
197
206
 
198
207
  ## Table of Contents
199
208
 
200
- - [Glimmer DSL for LibUI 0.2.8](#-glimmer-dsl-for-libui-028)
209
+ - [Glimmer DSL for LibUI](#)
201
210
  - [Glimmer GUI DSL Concepts](#glimmer-gui-dsl-concepts)
202
211
  - [Usage](#usage)
203
212
  - [Girb (Glimmer IRB)](#girb-glimmer-irb)
@@ -211,8 +220,10 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
211
220
  - [Table API](#table-api)
212
221
  - [Area API](#area-api)
213
222
  - [Smart Defaults and Conventions](#smart-defaults-and-conventions)
223
+ - [Custom Keywords](#custom-keywords)
214
224
  - [API Gotchas](#api-gotchas)
215
225
  - [Original API](#original-api)
226
+ - [Packaging](#packaging)
216
227
  - [Glimmer Style Guide](#glimmer-style-guide)
217
228
  - [Examples](#examples)
218
229
  - [Basic Window](#basic-window)
@@ -247,6 +258,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
247
258
  - [Color The Circles](#color-the-circles)
248
259
  - [Basic Draw Text](#basic-draw-text)
249
260
  - [Custom Draw Text](#custom-draw-text)
261
+ - [Method-Based Custom Keyword](#method-based-custom-keyword)
250
262
  - [Contributing to glimmer-dsl-libui](#contributing-to-glimmer-dsl-libui)
251
263
  - [Help](#help)
252
264
  - [Issues](#issues)
@@ -334,7 +346,7 @@ gem install glimmer-dsl-libui
334
346
  Or install via Bundler `Gemfile`:
335
347
 
336
348
  ```ruby
337
- gem 'glimmer-dsl-libui', '~> 0.2.8'
349
+ gem 'glimmer-dsl-libui', '~> 0.2.12'
338
350
  ```
339
351
 
340
352
  Add `require 'glimmer-dsl-libui'` at the top, and then `include Glimmer` into the top-level main object for testing or into an actual class for serious usage.
@@ -446,7 +458,7 @@ Control(Args) | Properties | Listeners
446
458
  `msg_box_error(window = main_window as Glimmer::LibUI::WindowProxy, title as String, description as String)` | None | None
447
459
  `non_wrapping_multiline_entry` | `read_only` (Boolean), `text` (`String`) | `on_changed`
448
460
  `password_entry` | `read_only` (Boolean), `text` (`String`) | `on_changed`
449
- `path(draw_fill_mode = :winding)` | `fill` (`Hash` of `:r` as `0`-`255`, `:g` as `0`-`255`, `:b` as `0`-`255`, `:a` as `0.0`-`1.0`), `stroke` (`Hash` of `:r` as `0`-`255`, `:g` as `0`-`255`, `:b` as `0`-`255`, `:a` as `0.0`-`1.0`, `:cap` as (`:round`, `:square`, `:flat`), `:join` as (`:miter`, `:round`, `:bevel`), `:thickness` as `Numeric`, `:miter_limit` as `Numeric`, `:dashes` as `Array` of `Numeric` ) | None
461
+ `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
450
462
  `preferences_menu_item` | None | `on_clicked`
451
463
  `progress_bar` | `value` (`Numeric`) | None
452
464
  `progress_bar_column(name as String)` | None | None
@@ -457,7 +469,7 @@ Control(Args) | Properties | Listeners
457
469
  `slider(min as Numeric, max as Numeric)` | `value` (`Numeric`) | `on_changed`
458
470
  `spinbox(min as Numeric, max as Numeric)` | `value` (`Numeric`) | `on_changed`
459
471
  `square(x as Numeric, y as Numeric, length as Numeric)` | `x` (`Numeric`), `y` (`Numeric`), `length` (`Numeric`) | None
460
- `string` | `font`, `color`, `background`, `underline`, `underline_color`, `open_type_features` | None
472
+ `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
461
473
  `tab` | `margined` (Boolean), `num_pages` (`Integer`) | None
462
474
  `tab_item(name as String)` | `index` [read-only] (`Integer`), `margined` (Boolean), `name` [read-only] (`String`) | None
463
475
  `table` | `cell_rows` (`Array` (rows) of `Arrays` (row columns) of cell values (e.g. `String` values for `text_column` cells or `Array` of `image`/`String` for `image_text_column`)), `editable` as Boolean | `on_changed {|row, type, row_data| ...}`, `on_edited {|row, row_data| ...}`
@@ -801,19 +813,23 @@ Check [Basic Transform](#basic-transform) example for use of [X11](https://en.wi
801
813
 
802
814
  Check [Histogram](#histogram) example for use of hex colors.
803
815
 
804
- To draw `text` in an `area`, you simply nest a `text(x, y, width)` control directly under `area` or inside a `on_draw` listener, and then nest attributed `string {string_value}` controls underneath it returning an actual `String` (think of them as the `<span>` element in html, which contains a string of text).
816
+ To draw `text` in an `area`, you simply nest a `text(x, y, width)` control directly under `area` or inside a `on_draw` listener, and then nest attributed `string {[attributes]; string_value}` controls underneath it returning an actual `String` (think of them as the `<span>` or `<p>` element in html, which contains a string of text). Alternatively, you can nest attributed `string(string_value) {[attributes]}` if `string_value` is a short single-line string. An attributed `string` value can be changed dynamically via its `string` property.
805
817
 
806
- `text` control can have the following properties:
818
+ `text` has the following properties:
807
819
  - `default_font`:
808
820
  - `align`: `:left` (default), `:center`, or `:right` (`align` currently seems not to work on the Mac)
821
+ - `x`: x coordinate in relation to parent `area` top-left corner
822
+ - `y`: y coordinate in relation to parent `area` top-left corner
823
+ - `width` (default: area width - x*2): width of text to display
809
824
 
810
- `string` can have the following properties:
825
+ `string` has the following properties:
811
826
  - `font`: font descriptor hash consisting of `:family`, `:size`, `:weight` (`[:minimum, :thin, :ultra_light, :light, :book, :normal, :medium, :semi_bold, :bold, :ultra_bold, :heavy, :ultra_heavy, :maximum]`), `:italic` (`[:normal, :oblique, :italic]`), and `:stretch` (`[:ultra_condensed, :extra_condensed, :condensed, :semi_condensed, :normal, :semi_expanded, :expanded, :extra_expanded, :ultra_expanded]`) key values
812
827
  - `color`: rgba, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color
813
828
  - `background`: rgba, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color
814
829
  - `underline`: one of `:none`, `:single`, `:double`, `:suggestion`, `:color_custom`, `:color_spelling`, `:color_grammar`, `:color_auxiliary`
815
830
  - `underline_color`: one of `:spelling`, `:grammar`, `:auxiliary`, rgba, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color
816
- - `open_type_features`: it must have a block containing `open_type_tag` occurrances, which take the a, b, c, d arguments plus a number at the end.
831
+ - `open_type_features`: Open Type Features (https://www.microsoft.com/typography/otspec/featuretags.htm) consist of `open_type_tag`s nested in content block, which accept (`a`, `b`, `c`, `d`, `Integer`) arguments.
832
+ - `string`: string value (`String`)
817
833
 
818
834
  Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
819
835
 
@@ -834,12 +850,13 @@ window('area text drawing') {
834
850
  open_type_tag 'l', 'i', 'g', 'a', 1
835
851
  }
836
852
 
837
- "This is a test\n\n"
853
+ "This is a demonstration\n" \
854
+ "of a very long\n" \
855
+ "attributed string\n" \
856
+ "spanning multiple lines\n\n"
838
857
  }
839
858
 
840
- string {
841
- 'This is another test'
842
- }
859
+ string('This is a short unattributed string')
843
860
  }
844
861
  }
845
862
  }.show
@@ -876,12 +893,121 @@ window('area text drawing') {
876
893
  - `area` paths are specified declaratively with figures underneath (e.g. `rectangle`) and `area` draw listener is automatically generated
877
894
  - Observe figure properties (e.g. `rectangle` `width`) for changes and automatically redraw containing area accordingly
878
895
  - Observe `path` `fill` and `stroke` hashes for changes and automatically redraw containing area accordingly
896
+ - Observe `text` and `string` properties for changes and automatically redraw containing area accordingly
879
897
  - All controls are protected from garbage collection until no longer needed (explicitly destroyed), so there is no need to worry about surprises.
880
898
  - All resources are freed automatically once no longer needed or left to garbage collection.
881
899
  - 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.
882
900
  - Colors may be passed in as a hash of `:r`, `:g`, `:b`, `:a`, or `:red`, `:green`, `:blue`, `:alpha`, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color like `:skyblue`, or 6-number hex or 3-number hex (as `Integer` or `String` with or without `0x` prefix)
883
901
  - Color alpha value defaults to `1.0` when not specified.
884
902
 
903
+ ### Custom Keywords
904
+
905
+ To define custom keywords, simply define a method representing the custom control you want. To make reusable, you can define in modules and simply include the modules in the view classes that need them.
906
+
907
+ Example that defines `form_field`, `address_form`, `label_pair`, and `address` keywords (you may copy/paste in [`girb`](#girb-glimmer-irb)):
908
+
909
+ ```ruby
910
+ require 'glimmer-dsl-libui'
911
+ require 'facets'
912
+
913
+ include Glimmer
914
+
915
+ Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
916
+
917
+ def form_field(model, property)
918
+ property = property.to_s
919
+ entry { |e|
920
+ label property.underscore.split('_').map(&:capitalize).join(' ')
921
+ text model.send(property).to_s
922
+
923
+ on_changed do
924
+ model.send("#{property}=", e.text)
925
+ end
926
+ }
927
+ end
928
+
929
+ def address_form(address)
930
+ form {
931
+ form_field(address, :street)
932
+ form_field(address, :p_o_box)
933
+ form_field(address, :city)
934
+ form_field(address, :state)
935
+ form_field(address, :zip_code)
936
+ }
937
+ end
938
+
939
+ def label_pair(model, attribute, value)
940
+ name_label = nil
941
+ value_label = nil
942
+ horizontal_box {
943
+ name_label = label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
944
+ value_label = label(value.to_s)
945
+ }
946
+ Glimmer::DataBinding::Observer.proc do
947
+ value_label.text = model.send(attribute)
948
+ end.observe(model, attribute)
949
+ end
950
+
951
+ def address(address)
952
+ vertical_box {
953
+ address.each_pair do |attribute, value|
954
+ label_pair(address, attribute, value)
955
+ end
956
+ }
957
+ end
958
+
959
+ address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
960
+ address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
961
+
962
+ window('Method-Based Custom Keyword') {
963
+ margined true
964
+
965
+ horizontal_box {
966
+ vertical_box {
967
+ label('Address 1') {
968
+ stretchy false
969
+ }
970
+
971
+ address_form(address1)
972
+
973
+ horizontal_separator {
974
+ stretchy false
975
+ }
976
+
977
+ label('Address 1 (Saved)') {
978
+ stretchy false
979
+ }
980
+
981
+ address(address1)
982
+ }
983
+
984
+ vertical_separator {
985
+ stretchy false
986
+ }
987
+
988
+ vertical_box {
989
+ label('Address 2') {
990
+ stretchy false
991
+ }
992
+
993
+ address_form(address2)
994
+
995
+ horizontal_separator {
996
+ stretchy false
997
+ }
998
+
999
+ label('Address 2 (Saved)') {
1000
+ stretchy false
1001
+ }
1002
+
1003
+ address(address2)
1004
+ }
1005
+ }
1006
+ }.show
1007
+ ```
1008
+
1009
+ ![glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png)
1010
+
885
1011
  ### API Gotchas
886
1012
 
887
1013
  - There is no proper way to destroy `grid` children due to [libui](https://github.com/andlabs/libui) not offering any API for deleting them from `grid` (no `grid_delete` similar to `box_delete` for `horizontal_box` and `vertical_box`).
@@ -890,8 +1016,20 @@ window('area text drawing') {
890
1016
 
891
1017
  ### Original API
892
1018
 
893
- To learn more about the [LibUI](https://github.com/kojix2/LibUI) API exposed through [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui),
894
- check out the [libui C headers](https://github.com/andlabs/libui/blob/master/ui.h)
1019
+ To learn more about the [LibUI](https://github.com/kojix2/LibUI) API exposed through [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui):
1020
+ - Check out [LibUI ffi.rb](https://github.com/kojix2/LibUI/blob/main/lib/libui/ffi.rb)
1021
+ - Check out the [libui C headers](https://github.com/andlabs/libui/blob/master/ui.h)
1022
+ - Check out the [Go UI (Golang LibUI) documentation](https://pkg.go.dev/github.com/andlabs/ui) for an alternative well-documented [libui](https://github.com/andlabs/libui) reference.
1023
+
1024
+ ## Packaging
1025
+
1026
+ I am documenting options for packaging, which I have not tried myself, but figured they would still be useful to add to the README.md until I can expand further effort into supporting packaging.
1027
+
1028
+ For Windows, the [LibUI](https://github.com/kojix2/LibUI) project recommends [OCRA](https://github.com/larsch/ocra) (One-Click Ruby Application), which builds Windows executables from Ruby source.
1029
+
1030
+ For Mac, consider [Platybus](https://github.com/sveinbjornt/Platypus) (builds a native Mac app from a Ruby script)
1031
+
1032
+ For Linux, simply package your app as a [Ruby Gem](https://guides.rubygems.org/what-is-a-gem/) and [build rpm package from Ruby Gem](https://www.redpill-linpro.com/sysadvent/2015/12/07/building-rpms-from-gems.html) or [build deb package from Ruby Gem](https://openpreservation.org/blogs/building-debian-package-ruby-program/).
895
1033
 
896
1034
  ## Glimmer Style Guide
897
1035
 
@@ -908,8 +1046,6 @@ The following examples include reimplementions of the examples in the [LibUI](ht
908
1046
 
909
1047
  To browse all examples, simply launch the [Meta-Example](examples/meta_example.rb), which lists all examples and displays each example's code when selected. It also enables code editing to facilitate experimentation and learning.
910
1048
 
911
- (note that for examples that emit output to terminal/command-line via `p` or `puts`, you must run them directly to see output)
912
-
913
1049
  [examples/meta_example.rb](examples/meta_example.rb)
914
1050
 
915
1051
  Run with this command from the root of the project if you cloned the project:
@@ -941,59 +1077,122 @@ require 'facets'
941
1077
  class MetaExample
942
1078
  include Glimmer
943
1079
 
1080
+ def initialize
1081
+ @selected_example_index = 0
1082
+ end
1083
+
944
1084
  def examples
945
1085
  if @examples.nil?
946
1086
  example_files = Dir.glob(File.join(File.expand_path('.', __dir__), '**', '*.rb'))
947
1087
  example_file_names = example_files.map { |f| File.basename(f, '.rb') }
948
- example_file_names = example_file_names.reject { |f| f == 'meta_example' }
1088
+ example_file_names = example_file_names.reject { |f| f == 'meta_example' || f.match(/\d$/) }
949
1089
  @examples = example_file_names.map { |f| f.underscore.titlecase }
950
1090
  end
951
1091
  @examples
952
1092
  end
953
1093
 
1094
+ def examples_with_versions
1095
+ examples.map do |example|
1096
+ version_count_for(example) > 1 ? "#{example} (#{version_count_for(example)} versions)" : example
1097
+ end
1098
+ end
1099
+
954
1100
  def file_path_for(example)
955
1101
  File.join(File.expand_path('.', __dir__), "#{example.underscore}.rb")
956
1102
  end
957
1103
 
1104
+ def version_count_for(example)
1105
+ Dir.glob(File.join(File.expand_path('.', __dir__), "#{example.underscore}*.rb")).select {|file| file.match(/\d\.rb$/)}.count + 1
1106
+ end
1107
+
958
1108
  def glimmer_dsl_libui_file
959
1109
  File.expand_path('../lib/glimmer-dsl-libui', __dir__)
960
1110
  end
961
1111
 
1112
+ def selected_example
1113
+ examples[@selected_example_index]
1114
+ end
1115
+
1116
+ def run_example(example)
1117
+ command = "ruby -r #{glimmer_dsl_libui_file} #{example} 2>&1"
1118
+ result = ''
1119
+ IO.popen(command) do |f|
1120
+ f.each_line do |line|
1121
+ result << line
1122
+ puts line
1123
+ end
1124
+ end
1125
+ msg_box('Error Running Example', result) if result.downcase.include?('error')
1126
+ end
1127
+
962
1128
  def launch
963
1129
  window('Meta-Example', 700, 500) {
964
1130
  margined true
965
1131
 
966
1132
  horizontal_box {
967
1133
  vertical_box {
968
- @rbs = radio_buttons {
1134
+ stretchy false
1135
+
1136
+ @example_radio_buttons = radio_buttons {
969
1137
  stretchy false
970
- items examples
971
- selected 0
1138
+ items examples_with_versions
1139
+ selected @selected_example_index
972
1140
 
973
1141
  on_selected do
974
- @nwme.text = File.read(file_path_for(@examples[@rbs.selected]))
1142
+ @selected_example_index = @example_radio_buttons.selected
1143
+ example = selected_example
1144
+ @code_entry.text = File.read(file_path_for(example))
1145
+ @version_spinbox.value = 1
975
1146
  end
976
1147
  }
977
- button('Launch') {
1148
+
1149
+ horizontal_box {
1150
+ label('Version') {
1151
+ stretchy false
1152
+ }
1153
+
1154
+ @version_spinbox = spinbox(1, 100) {
1155
+ value 1
1156
+
1157
+ on_changed do
1158
+ example = selected_example
1159
+ if @version_spinbox.value > version_count_for(example)
1160
+ @version_spinbox.value -= 1
1161
+ else
1162
+ version_number = @version_spinbox.value == 1 ? '' : @version_spinbox.value
1163
+ example = "#{selected_example}#{version_number}"
1164
+ @code_entry.text = File.read(file_path_for(example))
1165
+ end
1166
+ end
1167
+ }
1168
+ }
1169
+
1170
+ horizontal_box {
978
1171
  stretchy false
979
1172
 
980
- on_clicked do
981
- begin
982
- meta_example_file = File.join(Dir.home, '.meta_example.rb')
983
- File.write(meta_example_file, @nwme.text)
984
- result = `ruby -r #{glimmer_dsl_libui_file} #{meta_example_file} 2>&1`
985
- msg_box('Error Running Example', result) if result.include?('error')
986
- rescue => e
987
- puts 'Unable to write code changes! Running original example...'
988
- system "ruby -r #{glimmer_dsl_libui_file} #{file_path_for(@examples[@rbs.selected])}"
1173
+ button('Launch') {
1174
+ on_clicked do
1175
+ begin
1176
+ meta_example_file = File.join(Dir.home, '.meta_example.rb')
1177
+ File.write(meta_example_file, @code_entry.text)
1178
+ run_example(meta_example_file)
1179
+ rescue => e
1180
+ puts e.full_message
1181
+ puts 'Unable to write code changes! Running original example...'
1182
+ run_example(file_path_for(selected_example))
1183
+ end
989
1184
  end
990
- end
1185
+ }
1186
+ button('Reset') {
1187
+ on_clicked do
1188
+ @code_entry.text = File.read(file_path_for(selected_example))
1189
+ end
1190
+ }
991
1191
  }
992
1192
  }
993
- vertical_box {
994
- @nwme = non_wrapping_multiline_entry {
995
- text File.read(file_path_for(@examples[@rbs.selected]))
996
- }
1193
+
1194
+ @code_entry = non_wrapping_multiline_entry {
1195
+ text File.read(file_path_for(selected_example))
997
1196
  }
998
1197
  }
999
1198
  }.show
@@ -2060,6 +2259,8 @@ include Glimmer
2060
2259
 
2061
2260
  window('color button', 230) {
2062
2261
  color_button { |cb|
2262
+ color :blue
2263
+
2063
2264
  on_changed do
2064
2265
  rgba = cb.color
2065
2266
  p rgba
@@ -2342,11 +2543,21 @@ window('Form') {
2342
2543
  @last_name_entry = entry {
2343
2544
  label 'Last Name' # label property is available when control is nested under form
2344
2545
  }
2546
+
2547
+ @phone_entry = entry {
2548
+ label 'Phone' # label property is available when control is nested under form
2549
+ }
2550
+
2551
+ @email_entry = entry {
2552
+ label 'Email' # label property is available when control is nested under form
2553
+ }
2345
2554
  }
2346
2555
 
2347
- button('Display Name') {
2556
+ button('Display Info') {
2557
+ stretchy false
2558
+
2348
2559
  on_clicked do
2349
- msg_box('Name', "#{@first_name_entry.text} #{@last_name_entry.text}")
2560
+ msg_box('Info', "#{@first_name_entry.text} #{@last_name_entry.text} has phone #{@phone_entry.text} and email #{@email_entry.text}")
2350
2561
  end
2351
2562
  }
2352
2563
  }
@@ -3056,7 +3267,7 @@ window('Task Progress', 300, 200) {
3056
3267
 
3057
3268
  on_clicked do
3058
3269
  data.each_with_index do |row_data, row|
3059
- data[row] = [row_data[0], 100] # automatically updates table due to implicit data-binding
3270
+ data[row][1] = 100 # automatically updates table due to implicit data-binding
3060
3271
  end
3061
3272
  end
3062
3273
  }
@@ -3650,14 +3861,15 @@ window('Area Gallery', 400, 400) {
3650
3861
  path { # declarative stable path
3651
3862
  square(0, 0, 100)
3652
3863
  square(100, 100, 400)
3653
-
3864
+
3654
3865
  fill r: 102, g: 102, b: 204
3655
3866
  }
3656
3867
  path { # declarative stable path
3657
3868
  rectangle(0, 100, 100, 400)
3658
3869
  rectangle(100, 0, 400, 100)
3659
-
3660
- fill r: 204, g: 102, b: 204
3870
+
3871
+ # linear gradient (has x0, y0, x1, y1, and stops)
3872
+ 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}]
3661
3873
  }
3662
3874
  path { # declarative stable path
3663
3875
  figure(100, 100) {
@@ -3683,17 +3895,24 @@ window('Area Gallery', 400, 400) {
3683
3895
  fill r: 202, g: 102, b: 204, a: 0.5
3684
3896
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3685
3897
  }
3898
+ path { # declarative stable path
3899
+ arc(400, 220, 180, 90, 90, false)
3900
+
3901
+ # radial gradient (has an outer_radius in addition to x0, y0, x1, y1, and stops)
3902
+ 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}]
3903
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3904
+ }
3686
3905
  path { # declarative stable path
3687
3906
  circle(200, 200, 90)
3688
3907
 
3689
3908
  fill r: 202, g: 102, b: 204, a: 0.5
3690
3909
  stroke r: 0, g: 0, b: 0, thickness: 2
3691
3910
  }
3692
- path { # declarative stable path
3693
- arc(400, 220, 180, 90, 90, false)
3694
-
3695
- fill r: 204, g: 102, b: 204, a: 0.5
3696
- stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3911
+ text(160, 40, 100) { # x, y, width
3912
+ string('Area Gallery') {
3913
+ font family: 'Times', size: 14
3914
+ color :black
3915
+ }
3697
3916
  }
3698
3917
 
3699
3918
  on_mouse_event do |area_mouse_event|
@@ -3784,7 +4003,8 @@ window('Area Gallery', 400, 400) {
3784
4003
  height 100
3785
4004
  }
3786
4005
 
3787
- fill r: 204, g: 102, b: 204
4006
+ # linear gradient (has x0, y0, x1, y1, and stops)
4007
+ 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}]
3788
4008
  }
3789
4009
  path { # declarative stable path
3790
4010
  figure {
@@ -3846,16 +4066,6 @@ window('Area Gallery', 400, 400) {
3846
4066
  fill r: 202, g: 102, b: 204, a: 0.5
3847
4067
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3848
4068
  }
3849
- path { # declarative stable path
3850
- circle {
3851
- x_center 200
3852
- y_center 200
3853
- radius 90
3854
- }
3855
-
3856
- fill r: 202, g: 102, b: 204, a: 0.5
3857
- stroke r: 0, g: 0, b: 0, thickness: 2
3858
- }
3859
4069
  path { # declarative stable path
3860
4070
  arc {
3861
4071
  x_center 400
@@ -3866,9 +4076,32 @@ window('Area Gallery', 400, 400) {
3866
4076
  is_negative false
3867
4077
  }
3868
4078
 
3869
- fill r: 204, g: 102, b: 204, a: 0.5
4079
+ # radial gradient (has an outer_radius in addition to x0, y0, x1, y1, and stops)
4080
+ 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}]
3870
4081
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3871
4082
  }
4083
+ path { # declarative stable path
4084
+ circle {
4085
+ x_center 200
4086
+ y_center 200
4087
+ radius 90
4088
+ }
4089
+
4090
+ fill r: 202, g: 102, b: 204, a: 0.5
4091
+ stroke r: 0, g: 0, b: 0, thickness: 2
4092
+ }
4093
+ text {
4094
+ x 160
4095
+ y 40
4096
+ width 100
4097
+
4098
+ string {
4099
+ font family: 'Times', size: 14
4100
+ color :black
4101
+
4102
+ 'Area Gallery'
4103
+ }
4104
+ }
3872
4105
 
3873
4106
  on_mouse_event do |area_mouse_event|
3874
4107
  p area_mouse_event
@@ -3941,7 +4174,8 @@ window('Area Gallery', 400, 400) {
3941
4174
  rectangle(0, 100, 100, 400)
3942
4175
  rectangle(100, 0, 400, 100)
3943
4176
 
3944
- fill r: 204, g: 102, b: 204
4177
+ # linear gradient (has x0, y0, x1, y1, and stops)
4178
+ 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}]
3945
4179
  }
3946
4180
  path { # a dynamic path is added semi-declaratively inside on_draw block
3947
4181
  figure(100, 100) {
@@ -3967,17 +4201,24 @@ window('Area Gallery', 400, 400) {
3967
4201
  fill r: 202, g: 102, b: 204, a: 0.5
3968
4202
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3969
4203
  }
4204
+ path { # a dynamic path is added semi-declaratively inside on_draw block
4205
+ arc(400, 220, 180, 90, 90, false)
4206
+
4207
+ # radial gradient (has an outer_radius in addition to x0, y0, x1, y1, and stops)
4208
+ 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}]
4209
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
4210
+ }
3970
4211
  path { # a dynamic path is added semi-declaratively inside on_draw block
3971
4212
  circle(200, 200, 90)
3972
4213
 
3973
4214
  fill r: 202, g: 102, b: 204, a: 0.5
3974
4215
  stroke r: 0, g: 0, b: 0, thickness: 2
3975
4216
  }
3976
- path { # a dynamic path is added semi-declaratively inside on_draw block
3977
- arc(400, 220, 180, 90, 90, false)
3978
-
3979
- fill r: 204, g: 102, b: 204, a: 0.5
3980
- stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
4217
+ text(160, 40, 100) { # x, y, width
4218
+ string('Area Gallery') {
4219
+ font family: 'Times', size: 14
4220
+ color :black
4221
+ }
3981
4222
  }
3982
4223
  end
3983
4224
 
@@ -4070,7 +4311,8 @@ window('Area Gallery', 400, 400) {
4070
4311
  height 100
4071
4312
  }
4072
4313
 
4073
- fill r: 204, g: 102, b: 204
4314
+ # linear gradient (has x0, y0, x1, y1, and stops)
4315
+ 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}]
4074
4316
  }
4075
4317
  path { # a dynamic path is added semi-declaratively inside on_draw block
4076
4318
  figure {
@@ -4132,16 +4374,6 @@ window('Area Gallery', 400, 400) {
4132
4374
  fill r: 202, g: 102, b: 204, a: 0.5
4133
4375
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
4134
4376
  }
4135
- path { # a dynamic path is added semi-declaratively inside on_draw block
4136
- circle {
4137
- x_center 200
4138
- y_center 200
4139
- radius 90
4140
- }
4141
-
4142
- fill r: 202, g: 102, b: 204, a: 0.5
4143
- stroke r: 0, g: 0, b: 0, thickness: 2
4144
- }
4145
4377
  path { # a dynamic path is added semi-declaratively inside on_draw block
4146
4378
  arc {
4147
4379
  x_center 400
@@ -4152,9 +4384,32 @@ window('Area Gallery', 400, 400) {
4152
4384
  is_negative false
4153
4385
  }
4154
4386
 
4155
- fill r: 204, g: 102, b: 204, a: 0.5
4387
+ # radial gradient (has an outer_radius in addition to x0, y0, x1, y1, and stops)
4388
+ 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}]
4156
4389
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
4157
4390
  }
4391
+ path { # a dynamic path is added semi-declaratively inside on_draw block
4392
+ circle {
4393
+ x_center 200
4394
+ y_center 200
4395
+ radius 90
4396
+ }
4397
+
4398
+ fill r: 202, g: 102, b: 204, a: 0.5
4399
+ stroke r: 0, g: 0, b: 0, thickness: 2
4400
+ }
4401
+ text {
4402
+ x 160
4403
+ y 40
4404
+ width 100
4405
+
4406
+ string {
4407
+ font family: 'Times', size: 14
4408
+ color :black
4409
+
4410
+ 'Area Gallery'
4411
+ }
4412
+ }
4158
4413
  end
4159
4414
 
4160
4415
  on_mouse_event do |area_mouse_event|
@@ -5578,7 +5833,132 @@ class CustomDrawText
5578
5833
  end
5579
5834
 
5580
5835
  CustomDrawText.new.launch
5836
+ ```
5837
+
5838
+ ### Method-Based Custom Keyword
5839
+
5840
+ [examples/method_based_custom_keyword.rb](examples/method_based_custom_keyword.rb)
5841
+
5842
+ Run with this command from the root of the project if you cloned the project:
5843
+
5844
+ ```
5845
+ ruby -r './lib/glimmer-dsl-libui' examples/method_based_custom_keyword.rb
5846
+ ```
5581
5847
 
5848
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
5849
+
5850
+ ```
5851
+ ruby -r glimmer-dsl-libui -e "require 'examples/method_based_custom_keyword'"
5852
+ ```
5853
+
5854
+ Mac
5855
+
5856
+ ![glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png)
5857
+
5858
+ Linux
5859
+
5860
+ ![glimmer-dsl-libui-linux-method-based-custom-keyword.png](images/glimmer-dsl-libui-linux-method-based-custom-keyword.png)
5861
+
5862
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
5863
+
5864
+ ```ruby
5865
+ require 'glimmer-dsl-libui'
5866
+ require 'facets'
5867
+
5868
+ include Glimmer
5869
+
5870
+ Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
5871
+
5872
+ def form_field(model, property)
5873
+ property = property.to_s
5874
+ entry { |e|
5875
+ label property.underscore.split('_').map(&:capitalize).join(' ')
5876
+ text model.send(property).to_s
5877
+
5878
+ on_changed do
5879
+ model.send("#{property}=", e.text)
5880
+ end
5881
+ }
5882
+ end
5883
+
5884
+ def address_form(address)
5885
+ form {
5886
+ form_field(address, :street)
5887
+ form_field(address, :p_o_box)
5888
+ form_field(address, :city)
5889
+ form_field(address, :state)
5890
+ form_field(address, :zip_code)
5891
+ }
5892
+ end
5893
+
5894
+ def label_pair(model, attribute, value)
5895
+ name_label = nil
5896
+ value_label = nil
5897
+ horizontal_box {
5898
+ name_label = label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
5899
+ value_label = label(value.to_s)
5900
+ }
5901
+ Glimmer::DataBinding::Observer.proc do
5902
+ value_label.text = model.send(attribute)
5903
+ end.observe(model, attribute)
5904
+ end
5905
+
5906
+ def address(address)
5907
+ vertical_box {
5908
+ address.each_pair do |attribute, value|
5909
+ label_pair(address, attribute, value)
5910
+ end
5911
+ }
5912
+ end
5913
+
5914
+ address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
5915
+ address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
5916
+
5917
+ window('Method-Based Custom Keyword') {
5918
+ margined true
5919
+
5920
+ horizontal_box {
5921
+ vertical_box {
5922
+ label('Address 1') {
5923
+ stretchy false
5924
+ }
5925
+
5926
+ address_form(address1)
5927
+
5928
+ horizontal_separator {
5929
+ stretchy false
5930
+ }
5931
+
5932
+ label('Address 1 (Saved)') {
5933
+ stretchy false
5934
+ }
5935
+
5936
+ address(address1)
5937
+ }
5938
+
5939
+ vertical_separator {
5940
+ stretchy false
5941
+ }
5942
+
5943
+ vertical_box {
5944
+ label('Address 2') {
5945
+ stretchy false
5946
+ }
5947
+
5948
+ address_form(address2)
5949
+
5950
+ horizontal_separator {
5951
+ stretchy false
5952
+ }
5953
+
5954
+ label('Address 2 (Saved)') {
5955
+ stretchy false
5956
+ }
5957
+
5958
+ address(address2)
5959
+ }
5960
+ }
5961
+ }.show
5582
5962
  ```
5583
5963
 
5584
5964
  ## Contributing to glimmer-dsl-libui