glimmer-dsl-libui 0.2.9 → 0.2.13

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.9
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.13
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
 
@@ -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,26 @@ 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
  }
120
- path { # declarative stable path
121
- circle(200, 200, 90)
122
-
123
- fill r: 202, g: 102, b: 204, a: 0.5
124
- stroke r: 0, g: 0, b: 0, thickness: 2
125
- }
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
122
+ unless OS.windows?
123
+ path { # declarative stable path
124
+ arc(400, 220, 180, 90, 90, false)
125
+
126
+ # radial gradient (has an outer_radius in addition to x0, y0, x1, y1, and stops)
127
+ 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}]
128
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
129
+ }
130
+ path { # declarative stable path
131
+ circle(200, 200, 90)
132
+
133
+ fill r: 202, g: 102, b: 204, a: 0.5
134
+ stroke r: 0, g: 0, b: 0, thickness: 2
135
+ }
136
+ end
137
+ text(160, 40, 100) { # x, y, width
138
+ string('Area Gallery') {
139
+ font family: 'Times', size: 14
140
+ color :black
141
+ }
131
142
  }
132
143
 
133
144
  on_mouse_event do |area_mouse_event|
@@ -186,7 +197,7 @@ window('Area Gallery', 400, 400) {
186
197
 
187
198
  [Check Out Many More Examples Over Here!](#examples)
188
199
 
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.
200
+ 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
201
 
191
202
  Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interested in:
192
203
  - [glimmer-dsl-swt](https://github.com/AndyObtiva/glimmer-dsl-swt): Glimmer DSL for SWT (JRuby Desktop Development GUI Framework)
@@ -197,7 +208,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
197
208
 
198
209
  ## Table of Contents
199
210
 
200
- - [Glimmer DSL for LibUI 0.2.9](#-glimmer-dsl-for-libui-029)
211
+ - [Glimmer DSL for LibUI](#)
201
212
  - [Glimmer GUI DSL Concepts](#glimmer-gui-dsl-concepts)
202
213
  - [Usage](#usage)
203
214
  - [Girb (Glimmer IRB)](#girb-glimmer-irb)
@@ -211,8 +222,10 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
211
222
  - [Table API](#table-api)
212
223
  - [Area API](#area-api)
213
224
  - [Smart Defaults and Conventions](#smart-defaults-and-conventions)
225
+ - [Custom Keywords](#custom-keywords)
214
226
  - [API Gotchas](#api-gotchas)
215
227
  - [Original API](#original-api)
228
+ - [Packaging](#packaging)
216
229
  - [Glimmer Style Guide](#glimmer-style-guide)
217
230
  - [Examples](#examples)
218
231
  - [Basic Window](#basic-window)
@@ -244,9 +257,10 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
244
257
  - [Basic Transform](#basic-transform)
245
258
  - [Login](#login)
246
259
  - [Timer](#timer)
247
- - [Color The Circles](#color-the-circles)
260
+ - [Color The Shapes](#color-the-shapes)
248
261
  - [Basic Draw Text](#basic-draw-text)
249
262
  - [Custom Draw Text](#custom-draw-text)
263
+ - [Method-Based Custom Keyword](#method-based-custom-keyword)
250
264
  - [Contributing to glimmer-dsl-libui](#contributing-to-glimmer-dsl-libui)
251
265
  - [Help](#help)
252
266
  - [Issues](#issues)
@@ -334,7 +348,7 @@ gem install glimmer-dsl-libui
334
348
  Or install via Bundler `Gemfile`:
335
349
 
336
350
  ```ruby
337
- gem 'glimmer-dsl-libui', '~> 0.2.9'
351
+ gem 'glimmer-dsl-libui', '~> 0.2.13'
338
352
  ```
339
353
 
340
354
  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 +460,7 @@ Control(Args) | Properties | Listeners
446
460
  `msg_box_error(window = main_window as Glimmer::LibUI::WindowProxy, title as String, description as String)` | None | None
447
461
  `non_wrapping_multiline_entry` | `read_only` (Boolean), `text` (`String`) | `on_changed`
448
462
  `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
463
+ `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
464
  `preferences_menu_item` | None | `on_clicked`
451
465
  `progress_bar` | `value` (`Numeric`) | None
452
466
  `progress_bar_column(name as String)` | None | None
@@ -457,7 +471,7 @@ Control(Args) | Properties | Listeners
457
471
  `slider(min as Numeric, max as Numeric)` | `value` (`Numeric`) | `on_changed`
458
472
  `spinbox(min as Numeric, max as Numeric)` | `value` (`Numeric`) | `on_changed`
459
473
  `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
474
+ `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
475
  `tab` | `margined` (Boolean), `num_pages` (`Integer`) | None
462
476
  `tab_item(name as String)` | `index` [read-only] (`Integer`), `margined` (Boolean), `name` [read-only] (`String`) | None
463
477
  `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 +815,23 @@ Check [Basic Transform](#basic-transform) example for use of [X11](https://en.wi
801
815
 
802
816
  Check [Histogram](#histogram) example for use of hex colors.
803
817
 
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).
818
+ 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
819
 
806
- `text` control can have the following properties:
820
+ `text` has the following properties:
807
821
  - `default_font`:
808
822
  - `align`: `:left` (default), `:center`, or `:right` (`align` currently seems not to work on the Mac)
823
+ - `x`: x coordinate in relation to parent `area` top-left corner
824
+ - `y`: y coordinate in relation to parent `area` top-left corner
825
+ - `width` (default: area width - x*2): width of text to display
809
826
 
810
- `string` can have the following properties:
827
+ `string` has the following properties:
811
828
  - `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
829
  - `color`: rgba, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color
813
830
  - `background`: rgba, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color
814
831
  - `underline`: one of `:none`, `:single`, `:double`, `:suggestion`, `:color_custom`, `:color_spelling`, `:color_grammar`, `:color_auxiliary`
815
832
  - `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.
833
+ - `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.
834
+ - `string`: string value (`String`)
817
835
 
818
836
  Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
819
837
 
@@ -834,12 +852,13 @@ window('area text drawing') {
834
852
  open_type_tag 'l', 'i', 'g', 'a', 1
835
853
  }
836
854
 
837
- "This is a test\n\n"
855
+ "This is a demonstration\n" \
856
+ "of a very long\n" \
857
+ "attributed string\n" \
858
+ "spanning multiple lines\n\n"
838
859
  }
839
860
 
840
- string {
841
- 'This is another test'
842
- }
861
+ string('This is a short unattributed string')
843
862
  }
844
863
  }
845
864
  }.show
@@ -876,22 +895,147 @@ window('area text drawing') {
876
895
  - `area` paths are specified declaratively with figures underneath (e.g. `rectangle`) and `area` draw listener is automatically generated
877
896
  - Observe figure properties (e.g. `rectangle` `width`) for changes and automatically redraw containing area accordingly
878
897
  - Observe `path` `fill` and `stroke` hashes for changes and automatically redraw containing area accordingly
898
+ - Observe `text` and `string` properties for changes and automatically redraw containing area accordingly
879
899
  - All controls are protected from garbage collection until no longer needed (explicitly destroyed), so there is no need to worry about surprises.
880
900
  - All resources are freed automatically once no longer needed or left to garbage collection.
881
901
  - 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
902
  - 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
903
  - Color alpha value defaults to `1.0` when not specified.
884
904
 
905
+ ### Custom Keywords
906
+
907
+ 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.
908
+
909
+ Example that defines `form_field`, `address_form`, `label_pair`, and `address` keywords (you may copy/paste in [`girb`](#girb-glimmer-irb)):
910
+
911
+ ```ruby
912
+ require 'glimmer-dsl-libui'
913
+ require 'facets'
914
+
915
+ include Glimmer
916
+
917
+ Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
918
+
919
+ def form_field(model, property)
920
+ property = property.to_s
921
+ entry { |e|
922
+ label property.underscore.split('_').map(&:capitalize).join(' ')
923
+ text model.send(property).to_s
924
+
925
+ on_changed do
926
+ model.send("#{property}=", e.text)
927
+ end
928
+ }
929
+ end
930
+
931
+ def address_form(address)
932
+ form {
933
+ form_field(address, :street)
934
+ form_field(address, :p_o_box)
935
+ form_field(address, :city)
936
+ form_field(address, :state)
937
+ form_field(address, :zip_code)
938
+ }
939
+ end
940
+
941
+ def label_pair(model, attribute, value)
942
+ name_label = nil
943
+ value_label = nil
944
+ horizontal_box {
945
+ name_label = label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
946
+ value_label = label(value.to_s)
947
+ }
948
+ Glimmer::DataBinding::Observer.proc do
949
+ value_label.text = model.send(attribute)
950
+ end.observe(model, attribute)
951
+ end
952
+
953
+ def address(address)
954
+ vertical_box {
955
+ address.each_pair do |attribute, value|
956
+ label_pair(address, attribute, value)
957
+ end
958
+ }
959
+ end
960
+
961
+ address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
962
+ address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
963
+
964
+ window('Method-Based Custom Keyword') {
965
+ margined true
966
+
967
+ horizontal_box {
968
+ vertical_box {
969
+ label('Address 1') {
970
+ stretchy false
971
+ }
972
+
973
+ address_form(address1)
974
+
975
+ horizontal_separator {
976
+ stretchy false
977
+ }
978
+
979
+ label('Address 1 (Saved)') {
980
+ stretchy false
981
+ }
982
+
983
+ address(address1)
984
+ }
985
+
986
+ vertical_separator {
987
+ stretchy false
988
+ }
989
+
990
+ vertical_box {
991
+ label('Address 2') {
992
+ stretchy false
993
+ }
994
+
995
+ address_form(address2)
996
+
997
+ horizontal_separator {
998
+ stretchy false
999
+ }
1000
+
1001
+ label('Address 2 (Saved)') {
1002
+ stretchy false
1003
+ }
1004
+
1005
+ address(address2)
1006
+ }
1007
+ }
1008
+ }.show
1009
+ ```
1010
+
1011
+ ![glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png)
1012
+
885
1013
  ### API Gotchas
886
1014
 
887
1015
  - 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`).
888
1016
  - `table` `checkbox_column` and `checkbox_text_column` checkbox editing only works on Windows and Linux (not Mac) due to a current limitation in [libui](https://github.com/andlabs/ui/issues/357).
889
1017
  - `text` `align` property seems not to work on the Mac ([libui](https://github.com/andlabs/libui) has an [issue](https://github.com/andlabs/libui/pull/407) about it)
1018
+ - `text` `string` `background` does not work on Windows due to an issue in [libui](https://github.com/andlabs/libui).
1019
+ - `arc` shape does not work on Windows due to an issue in [libui](https://github.com/andlabs/libui).
1020
+ - `table` controls on Windows intentionally get an extra empty row at the end because if any row were to be deleted for the first time, double-deletion happens due to an issue in [libui](https://github.com/andlabs/libui) on Windows.
1021
+ - `table` `progress_bar` column on Windows cannot be updated with a positive value if it started initially with `-1` (it ignores update to avoid crashing due to an issue in [libui](https://github.com/andlabs/libui) on Windows.
890
1022
 
891
1023
  ### Original API
892
1024
 
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)
1025
+ 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):
1026
+ - Check out [LibUI ffi.rb](https://github.com/kojix2/LibUI/blob/main/lib/libui/ffi.rb)
1027
+ - Check out the [libui C headers](https://github.com/andlabs/libui/blob/master/ui.h)
1028
+ - 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.
1029
+
1030
+ ## Packaging
1031
+
1032
+ 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.
1033
+
1034
+ 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.
1035
+
1036
+ For Mac, consider [Platybus](https://github.com/sveinbjornt/Platypus) (builds a native Mac app from a Ruby script)
1037
+
1038
+ 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
1039
 
896
1040
  ## Glimmer Style Guide
897
1041
 
@@ -908,8 +1052,6 @@ The following examples include reimplementions of the examples in the [LibUI](ht
908
1052
 
909
1053
  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
1054
 
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
1055
  [examples/meta_example.rb](examples/meta_example.rb)
914
1056
 
915
1057
  Run with this command from the root of the project if you cloned the project:
@@ -941,59 +1083,122 @@ require 'facets'
941
1083
  class MetaExample
942
1084
  include Glimmer
943
1085
 
1086
+ def initialize
1087
+ @selected_example_index = 0
1088
+ end
1089
+
944
1090
  def examples
945
1091
  if @examples.nil?
946
1092
  example_files = Dir.glob(File.join(File.expand_path('.', __dir__), '**', '*.rb'))
947
1093
  example_file_names = example_files.map { |f| File.basename(f, '.rb') }
948
- example_file_names = example_file_names.reject { |f| f == 'meta_example' }
1094
+ example_file_names = example_file_names.reject { |f| f == 'meta_example' || f.match(/\d$/) }
949
1095
  @examples = example_file_names.map { |f| f.underscore.titlecase }
950
1096
  end
951
1097
  @examples
952
1098
  end
953
1099
 
1100
+ def examples_with_versions
1101
+ examples.map do |example|
1102
+ version_count_for(example) > 1 ? "#{example} (#{version_count_for(example)} versions)" : example
1103
+ end
1104
+ end
1105
+
954
1106
  def file_path_for(example)
955
1107
  File.join(File.expand_path('.', __dir__), "#{example.underscore}.rb")
956
1108
  end
957
1109
 
1110
+ def version_count_for(example)
1111
+ Dir.glob(File.join(File.expand_path('.', __dir__), "#{example.underscore}*.rb")).select {|file| file.match(/\d\.rb$/)}.count + 1
1112
+ end
1113
+
958
1114
  def glimmer_dsl_libui_file
959
1115
  File.expand_path('../lib/glimmer-dsl-libui', __dir__)
960
1116
  end
961
1117
 
1118
+ def selected_example
1119
+ examples[@selected_example_index]
1120
+ end
1121
+
1122
+ def run_example(example)
1123
+ command = "ruby -r #{glimmer_dsl_libui_file} #{example} 2>&1"
1124
+ result = ''
1125
+ IO.popen(command) do |f|
1126
+ f.each_line do |line|
1127
+ result << line
1128
+ puts line
1129
+ end
1130
+ end
1131
+ msg_box('Error Running Example', result) if result.downcase.include?('error')
1132
+ end
1133
+
962
1134
  def launch
963
1135
  window('Meta-Example', 700, 500) {
964
1136
  margined true
965
1137
 
966
1138
  horizontal_box {
967
1139
  vertical_box {
968
- @rbs = radio_buttons {
1140
+ stretchy false
1141
+
1142
+ @example_radio_buttons = radio_buttons {
969
1143
  stretchy false
970
- items examples
971
- selected 0
1144
+ items examples_with_versions
1145
+ selected @selected_example_index
972
1146
 
973
1147
  on_selected do
974
- @nwme.text = File.read(file_path_for(@examples[@rbs.selected]))
1148
+ @selected_example_index = @example_radio_buttons.selected
1149
+ example = selected_example
1150
+ @code_entry.text = File.read(file_path_for(example))
1151
+ @version_spinbox.value = 1
975
1152
  end
976
1153
  }
977
- button('Launch') {
1154
+
1155
+ horizontal_box {
1156
+ label('Version') {
1157
+ stretchy false
1158
+ }
1159
+
1160
+ @version_spinbox = spinbox(1, 100) {
1161
+ value 1
1162
+
1163
+ on_changed do
1164
+ example = selected_example
1165
+ if @version_spinbox.value > version_count_for(example)
1166
+ @version_spinbox.value -= 1
1167
+ else
1168
+ version_number = @version_spinbox.value == 1 ? '' : @version_spinbox.value
1169
+ example = "#{selected_example}#{version_number}"
1170
+ @code_entry.text = File.read(file_path_for(example))
1171
+ end
1172
+ end
1173
+ }
1174
+ }
1175
+
1176
+ horizontal_box {
978
1177
  stretchy false
979
1178
 
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])}"
1179
+ button('Launch') {
1180
+ on_clicked do
1181
+ begin
1182
+ meta_example_file = File.join(Dir.home, '.meta_example.rb')
1183
+ File.write(meta_example_file, @code_entry.text)
1184
+ run_example(meta_example_file)
1185
+ rescue => e
1186
+ puts e.full_message
1187
+ puts 'Unable to write code changes! Running original example...'
1188
+ run_example(file_path_for(selected_example))
1189
+ end
989
1190
  end
990
- end
1191
+ }
1192
+ button('Reset') {
1193
+ on_clicked do
1194
+ @code_entry.text = File.read(file_path_for(selected_example))
1195
+ end
1196
+ }
991
1197
  }
992
1198
  }
993
- vertical_box {
994
- @nwme = non_wrapping_multiline_entry {
995
- text File.read(file_path_for(@examples[@rbs.selected]))
996
- }
1199
+
1200
+ @code_entry = non_wrapping_multiline_entry {
1201
+ text File.read(file_path_for(selected_example))
997
1202
  }
998
1203
  }
999
1204
  }.show
@@ -2060,6 +2265,8 @@ include Glimmer
2060
2265
 
2061
2266
  window('color button', 230) {
2062
2267
  color_button { |cb|
2268
+ color :blue
2269
+
2063
2270
  on_changed do
2064
2271
  rgba = cb.color
2065
2272
  p rgba
@@ -2342,11 +2549,21 @@ window('Form') {
2342
2549
  @last_name_entry = entry {
2343
2550
  label 'Last Name' # label property is available when control is nested under form
2344
2551
  }
2552
+
2553
+ @phone_entry = entry {
2554
+ label 'Phone' # label property is available when control is nested under form
2555
+ }
2556
+
2557
+ @email_entry = entry {
2558
+ label 'Email' # label property is available when control is nested under form
2559
+ }
2345
2560
  }
2346
2561
 
2347
- button('Display Name') {
2562
+ button('Display Info') {
2563
+ stretchy false
2564
+
2348
2565
  on_clicked do
2349
- msg_box('Name', "#{@first_name_entry.text} #{@last_name_entry.text}")
2566
+ msg_box('Info', "#{@first_name_entry.text} #{@last_name_entry.text} has phone #{@phone_entry.text} and email #{@email_entry.text}")
2350
2567
  end
2351
2568
  }
2352
2569
  }
@@ -3650,14 +3867,15 @@ window('Area Gallery', 400, 400) {
3650
3867
  path { # declarative stable path
3651
3868
  square(0, 0, 100)
3652
3869
  square(100, 100, 400)
3653
-
3870
+
3654
3871
  fill r: 102, g: 102, b: 204
3655
3872
  }
3656
3873
  path { # declarative stable path
3657
3874
  rectangle(0, 100, 100, 400)
3658
3875
  rectangle(100, 0, 400, 100)
3659
-
3660
- fill r: 204, g: 102, b: 204
3876
+
3877
+ # linear gradient (has x0, y0, x1, y1, and stops)
3878
+ 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
3879
  }
3662
3880
  path { # declarative stable path
3663
3881
  figure(100, 100) {
@@ -3683,17 +3901,26 @@ window('Area Gallery', 400, 400) {
3683
3901
  fill r: 202, g: 102, b: 204, a: 0.5
3684
3902
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3685
3903
  }
3686
- path { # declarative stable path
3687
- circle(200, 200, 90)
3688
-
3689
- fill r: 202, g: 102, b: 204, a: 0.5
3690
- stroke r: 0, g: 0, b: 0, thickness: 2
3691
- }
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
3904
+ unless OS.windows?
3905
+ path { # declarative stable path
3906
+ arc(400, 220, 180, 90, 90, false)
3907
+
3908
+ # radial gradient (has an outer_radius in addition to x0, y0, x1, y1, and stops)
3909
+ 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}]
3910
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3911
+ }
3912
+ path { # declarative stable path
3913
+ circle(200, 200, 90)
3914
+
3915
+ fill r: 202, g: 102, b: 204, a: 0.5
3916
+ stroke r: 0, g: 0, b: 0, thickness: 2
3917
+ }
3918
+ end
3919
+ text(160, 40, 100) { # x, y, width
3920
+ string('Area Gallery') {
3921
+ font family: 'Times', size: 14
3922
+ color :black
3923
+ }
3697
3924
  }
3698
3925
 
3699
3926
  on_mouse_event do |area_mouse_event|
@@ -3784,7 +4011,8 @@ window('Area Gallery', 400, 400) {
3784
4011
  height 100
3785
4012
  }
3786
4013
 
3787
- fill r: 204, g: 102, b: 204
4014
+ # linear gradient (has x0, y0, x1, y1, and stops)
4015
+ 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
4016
  }
3789
4017
  path { # declarative stable path
3790
4018
  figure {
@@ -3846,28 +4074,43 @@ window('Area Gallery', 400, 400) {
3846
4074
  fill r: 202, g: 102, b: 204, a: 0.5
3847
4075
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3848
4076
  }
3849
- path { # declarative stable path
3850
- circle {
3851
- x_center 200
3852
- y_center 200
3853
- radius 90
4077
+ unless OS.windows?
4078
+ path { # declarative stable path
4079
+ arc {
4080
+ x_center 400
4081
+ y_center 220
4082
+ radius 180
4083
+ start_angle 90
4084
+ sweep 90
4085
+ is_negative false
4086
+ }
4087
+
4088
+ # radial gradient (has an outer_radius in addition to x0, y0, x1, y1, and stops)
4089
+ 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}]
4090
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3854
4091
  }
3855
-
3856
- fill r: 202, g: 102, b: 204, a: 0.5
3857
- stroke r: 0, g: 0, b: 0, thickness: 2
3858
- }
3859
- path { # declarative stable path
3860
- arc {
3861
- x_center 400
3862
- y_center 220
3863
- radius 180
3864
- start_angle 90
3865
- sweep 90
3866
- is_negative false
4092
+ path { # declarative stable path
4093
+ circle {
4094
+ x_center 200
4095
+ y_center 200
4096
+ radius 90
4097
+ }
4098
+
4099
+ fill r: 202, g: 102, b: 204, a: 0.5
4100
+ stroke r: 0, g: 0, b: 0, thickness: 2
4101
+ }
4102
+ end
4103
+ text {
4104
+ x 160
4105
+ y 40
4106
+ width 100
4107
+
4108
+ string {
4109
+ font family: 'Times', size: 14
4110
+ color :black
4111
+
4112
+ 'Area Gallery'
3867
4113
  }
3868
-
3869
- fill r: 204, g: 102, b: 204, a: 0.5
3870
- stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3871
4114
  }
3872
4115
 
3873
4116
  on_mouse_event do |area_mouse_event|
@@ -3941,7 +4184,8 @@ window('Area Gallery', 400, 400) {
3941
4184
  rectangle(0, 100, 100, 400)
3942
4185
  rectangle(100, 0, 400, 100)
3943
4186
 
3944
- fill r: 204, g: 102, b: 204
4187
+ # linear gradient (has x0, y0, x1, y1, and stops)
4188
+ 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
4189
  }
3946
4190
  path { # a dynamic path is added semi-declaratively inside on_draw block
3947
4191
  figure(100, 100) {
@@ -3967,17 +4211,26 @@ window('Area Gallery', 400, 400) {
3967
4211
  fill r: 202, g: 102, b: 204, a: 0.5
3968
4212
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3969
4213
  }
3970
- path { # a dynamic path is added semi-declaratively inside on_draw block
3971
- circle(200, 200, 90)
3972
-
3973
- fill r: 202, g: 102, b: 204, a: 0.5
3974
- stroke r: 0, g: 0, b: 0, thickness: 2
3975
- }
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
4214
+ unless OS.windows?
4215
+ path { # a dynamic path is added semi-declaratively inside on_draw block
4216
+ arc(400, 220, 180, 90, 90, false)
4217
+
4218
+ # radial gradient (has an outer_radius in addition to x0, y0, x1, y1, and stops)
4219
+ 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}]
4220
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
4221
+ }
4222
+ path { # a dynamic path is added semi-declaratively inside on_draw block
4223
+ circle(200, 200, 90)
4224
+
4225
+ fill r: 202, g: 102, b: 204, a: 0.5
4226
+ stroke r: 0, g: 0, b: 0, thickness: 2
4227
+ }
4228
+ end
4229
+ text(160, 40, 100) { # x, y, width
4230
+ string('Area Gallery') {
4231
+ font family: 'Times', size: 14
4232
+ color :black
4233
+ }
3981
4234
  }
3982
4235
  end
3983
4236
 
@@ -4070,7 +4323,8 @@ window('Area Gallery', 400, 400) {
4070
4323
  height 100
4071
4324
  }
4072
4325
 
4073
- fill r: 204, g: 102, b: 204
4326
+ # linear gradient (has x0, y0, x1, y1, and stops)
4327
+ 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
4328
  }
4075
4329
  path { # a dynamic path is added semi-declaratively inside on_draw block
4076
4330
  figure {
@@ -4132,28 +4386,43 @@ window('Area Gallery', 400, 400) {
4132
4386
  fill r: 202, g: 102, b: 204, a: 0.5
4133
4387
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
4134
4388
  }
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
4389
+ unless OS.windows?
4390
+ path { # a dynamic path is added semi-declaratively inside on_draw block
4391
+ arc {
4392
+ x_center 400
4393
+ y_center 220
4394
+ radius 180
4395
+ start_angle 90
4396
+ sweep 90
4397
+ is_negative false
4398
+ }
4399
+
4400
+ # radial gradient (has an outer_radius in addition to x0, y0, x1, y1, and stops)
4401
+ 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}]
4402
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
4140
4403
  }
4141
-
4142
- fill r: 202, g: 102, b: 204, a: 0.5
4143
- stroke r: 0, g: 0, b: 0, thickness: 2
4144
- }
4145
- path { # a dynamic path is added semi-declaratively inside on_draw block
4146
- arc {
4147
- x_center 400
4148
- y_center 220
4149
- radius 180
4150
- start_angle 90
4151
- sweep 90
4152
- is_negative false
4404
+ path { # a dynamic path is added semi-declaratively inside on_draw block
4405
+ circle {
4406
+ x_center 200
4407
+ y_center 200
4408
+ radius 90
4409
+ }
4410
+
4411
+ fill r: 202, g: 102, b: 204, a: 0.5
4412
+ stroke r: 0, g: 0, b: 0, thickness: 2
4413
+ }
4414
+ end
4415
+ text {
4416
+ x 160
4417
+ y 40
4418
+ width 100
4419
+
4420
+ string {
4421
+ font family: 'Times', size: 14
4422
+ color :black
4423
+
4424
+ 'Area Gallery'
4153
4425
  }
4154
-
4155
- fill r: 204, g: 102, b: 204, a: 0.5
4156
- stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
4157
4426
  }
4158
4427
  end
4159
4428
 
@@ -4845,70 +5114,74 @@ end
4845
5114
  Timer.new
4846
5115
  ```
4847
5116
 
4848
- ### Color The Circles
5117
+ ### Color The Shapes
4849
5118
 
4850
- [examples/color_the_circles.rb](examples/color_the_circles.rb)
5119
+ [examples/color_the_shapes.rb](examples/color_the_shapes.rb)
4851
5120
 
4852
5121
  Run with this command from the root of the project if you cloned the project:
4853
5122
 
4854
5123
  ```
4855
- ruby -r './lib/glimmer-dsl-libui' examples/color_the_circles.rb
5124
+ ruby -r './lib/glimmer-dsl-libui' examples/color_the_shapes.rb
4856
5125
  ```
4857
5126
 
4858
5127
  Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
4859
5128
 
4860
5129
  ```
4861
- ruby -r glimmer-dsl-libui -e "require 'examples/color_the_circles'"
5130
+ ruby -r glimmer-dsl-libui -e "require 'examples/color_the_shapes'"
4862
5131
  ```
4863
5132
 
4864
5133
  Mac
4865
5134
 
4866
- ![glimmer-dsl-libui-mac-color-the-circles.png](images/glimmer-dsl-libui-mac-color-the-circles.png)
4867
- ![glimmer-dsl-libui-mac-color-the-circles-lost.png](images/glimmer-dsl-libui-mac-color-the-circles-lost.png)
4868
- ![glimmer-dsl-libui-mac-color-the-circles-won.png](images/glimmer-dsl-libui-mac-color-the-circles-won.png)
5135
+ ![glimmer-dsl-libui-mac-color-the-shapes.png](images/glimmer-dsl-libui-mac-color-the-shapes.png)
5136
+ ![glimmer-dsl-libui-mac-color-the-shapes-lost.png](images/glimmer-dsl-libui-mac-color-the-shapes-lost.png)
5137
+ ![glimmer-dsl-libui-mac-color-the-shapes-won.png](images/glimmer-dsl-libui-mac-color-the-shapes-won.png)
4869
5138
 
4870
5139
  Linux
4871
5140
 
4872
- ![glimmer-dsl-libui-linux-color-the-circles.png](images/glimmer-dsl-libui-linux-color-the-circles.png)
4873
- ![glimmer-dsl-libui-linux-color-the-circles-lost.png](images/glimmer-dsl-libui-linux-color-the-circles-lost.png)
4874
- ![glimmer-dsl-libui-linux-color-the-circles-won.png](images/glimmer-dsl-libui-linux-color-the-circles-won.png)
5141
+ ![glimmer-dsl-libui-linux-color-the-shapes.png](images/glimmer-dsl-libui-linux-color-the-shapes.png)
5142
+ ![glimmer-dsl-libui-linux-color-the-shapes-lost.png](images/glimmer-dsl-libui-linux-color-the-shapes-lost.png)
5143
+ ![glimmer-dsl-libui-linux-color-the-shapes-won.png](images/glimmer-dsl-libui-linux-color-the-shapes-won.png)
4875
5144
 
4876
5145
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
4877
5146
 
4878
5147
  ```ruby
4879
5148
  require 'glimmer-dsl-libui'
4880
5149
 
4881
- class ColorTheCircles
5150
+ class ColorTheShapes
4882
5151
  include Glimmer
4883
5152
 
4884
5153
  WINDOW_WIDTH = 800
4885
5154
  WINDOW_HEIGHT = 600
4886
- CIRCLE_MIN_RADIUS = 15
4887
- CIRCLE_MAX_RADIUS = 50
5155
+ SHAPE_MIN_SIZE = 15
5156
+ SHAPE_MAX_SIZE = 100
4888
5157
  MARGIN_WIDTH = 55
4889
5158
  MARGIN_HEIGHT = 155
4890
5159
  TIME_MAX_EASY = 4
4891
5160
  TIME_MAX_MEDIUM = 3
4892
5161
  TIME_MAX_HARD = 2
4893
5162
  TIME_MAX_INSANE = 1
5163
+ SHAPES = ['square'] + (OS.windows? ? [] : ['circle'])
4894
5164
 
4895
5165
  attr_accessor :score
4896
5166
 
4897
5167
  def initialize
4898
- @circles_data = []
5168
+ @shapes_data = []
4899
5169
  @score = 0
4900
5170
  @time_max = TIME_MAX_HARD
5171
+ @game_over = false
4901
5172
  register_observers
4902
- setup_circle_factory
5173
+ setup_shape_factory
4903
5174
  end
4904
5175
 
4905
5176
  def register_observers
4906
5177
  observer = Glimmer::DataBinding::Observer.proc do |new_score|
4907
5178
  @score_label.text = new_score.to_s
4908
5179
  if new_score == -20
5180
+ @game_over = true
4909
5181
  msg_box('You Lost!', 'Sorry! Your score reached -20')
4910
5182
  restart_game
4911
5183
  elsif new_score == 0
5184
+ @game_over = true
4912
5185
  msg_box('You Won!', 'Congratulations! Your score reached 0')
4913
5186
  restart_game
4914
5187
  end
@@ -4916,13 +5189,15 @@ class ColorTheCircles
4916
5189
  observer.observe(self, :score) # automatically enhances self to become Glimmer::DataBinding::ObservableModel and notify observer on score attribute changes
4917
5190
  end
4918
5191
 
4919
- def setup_circle_factory
5192
+ def setup_shape_factory
4920
5193
  consumer = Proc.new do
4921
- if @circles_data.empty?
4922
- # start with 3 circles to make more challenging
4923
- add_circle until @circles_data.size > 3
4924
- else
4925
- add_circle
5194
+ unless @game_over
5195
+ if @shapes_data.empty?
5196
+ # start with 3 shapes to make more challenging
5197
+ add_shape until @shapes_data.size > 3
5198
+ else
5199
+ add_shape
5200
+ end
4926
5201
  end
4927
5202
  delay = rand * @time_max
4928
5203
  Glimmer::LibUI.timer(delay, repeat: false, &consumer)
@@ -4930,13 +5205,13 @@ class ColorTheCircles
4930
5205
  Glimmer::LibUI.queue_main(&consumer)
4931
5206
  end
4932
5207
 
4933
- def add_circle
4934
- circle_x_center = rand * (WINDOW_WIDTH - MARGIN_WIDTH - CIRCLE_MAX_RADIUS) + CIRCLE_MAX_RADIUS
4935
- circle_y_center = rand * (WINDOW_HEIGHT - MARGIN_HEIGHT - CIRCLE_MAX_RADIUS) + CIRCLE_MAX_RADIUS
4936
- circle_radius = rand * (CIRCLE_MAX_RADIUS - CIRCLE_MIN_RADIUS) + CIRCLE_MIN_RADIUS
5208
+ def add_shape
5209
+ shape_x = rand * (WINDOW_WIDTH - MARGIN_WIDTH - SHAPE_MAX_SIZE) + SHAPE_MAX_SIZE
5210
+ shape_y = rand * (WINDOW_HEIGHT - MARGIN_HEIGHT - SHAPE_MAX_SIZE) + SHAPE_MAX_SIZE
5211
+ shape_size = rand * (SHAPE_MAX_SIZE - SHAPE_MIN_SIZE) + SHAPE_MIN_SIZE
4937
5212
  stroke_color = Glimmer::LibUI.x11_colors.sample
4938
- @circles_data << {
4939
- args: [circle_x_center, circle_y_center, circle_radius],
5213
+ @shapes_data << {
5214
+ args: [shape_x, shape_y, shape_size],
4940
5215
  fill: nil,
4941
5216
  stroke: stroke_color
4942
5217
  }
@@ -4946,9 +5221,29 @@ class ColorTheCircles
4946
5221
 
4947
5222
  def restart_game
4948
5223
  @score = 0 # update variable directly to avoid notifying observers
4949
- @circles_data.clear
5224
+ @shapes_data.clear
5225
+ @game_over = false
4950
5226
  end
4951
5227
 
5228
+ def color_shape(x, y)
5229
+ clicked_shape_data = @shapes_data.find do |shape_data|
5230
+ shape_data[:fill].nil? && shape_data[:shape]&.include?(x, y)
5231
+ end
5232
+ if clicked_shape_data
5233
+ clicked_shape_data[:fill] = clicked_shape_data[:stroke]
5234
+ push_colored_shape_behind_uncolored_shapes(clicked_shape_data)
5235
+ @area.queue_redraw_all
5236
+ self.score += 1 # notifies score observers automatically of change
5237
+ end
5238
+ end
5239
+
5240
+ def push_colored_shape_behind_uncolored_shapes(colored_shape_data)
5241
+ removed_colored_shape_data = @shapes_data.delete(colored_shape_data)
5242
+ last_colored_shape_data = @shapes_data.select {|cd| cd[:fill]}.last
5243
+ last_colored_shape_data_index = @shapes_data.index(last_colored_shape_data) || -1
5244
+ @shapes_data.insert(last_colored_shape_data_index + 1, removed_colored_shape_data)
5245
+ end
5246
+
4952
5247
  def launch
4953
5248
  menu('Actions') {
4954
5249
  menu_item('Restart') {
@@ -4991,12 +5286,12 @@ class ColorTheCircles
4991
5286
  menu('Help') {
4992
5287
  menu_item('Instructions') {
4993
5288
  on_clicked do
4994
- msg_box('Instructions', "Score goes down as circles are added.\nIf it reaches -20, you lose!\n\nClick circles to color and score!\nOnce score reaches 0, you win!\n\nBeware of concealed light-colored circles!\nThey are revealed once darker circles intersect them.\n\nThere are four levels of difficulty.\nChange via difficulty menu if the game gets too tough.")
5289
+ msg_box('Instructions', "Score goes down as shapes are added.\nIf it reaches -20, you lose!\n\nClick shapes to color and score!\nOnce score reaches 0, you win!\n\nBeware of concealed light-colored shapes!\nThey are revealed once darker shapes intersect them.\n\nThere are four levels of difficulty.\nChange via difficulty menu if the game gets too tough.")
4995
5290
  end
4996
5291
  }
4997
5292
  }
4998
5293
 
4999
- window('Color The Circles', WINDOW_WIDTH, WINDOW_HEIGHT) {
5294
+ window('Color The Shapes', WINDOW_WIDTH, WINDOW_HEIGHT) {
5000
5295
  margined true
5001
5296
 
5002
5297
  grid {
@@ -5010,13 +5305,13 @@ class ColorTheCircles
5010
5305
  end
5011
5306
  }
5012
5307
 
5013
- label('Score goes down as circles are added. If it reaches -20, you lose!') {
5308
+ label('Score goes down as shapes are added. If it reaches -20, you lose!') {
5014
5309
  left 0
5015
5310
  top 1
5016
5311
  halign :center
5017
5312
  }
5018
5313
 
5019
- label('Click circles to color and score! Once score reaches 0, you win!') {
5314
+ label('Click shapes to color and score! Once score reaches 0, you win!') {
5020
5315
  left 0
5021
5316
  top 2
5022
5317
  halign :center
@@ -5036,51 +5331,41 @@ class ColorTheCircles
5036
5331
  }
5037
5332
  }
5038
5333
 
5039
- vertical_box {
5334
+ @area = area {
5040
5335
  left 0
5041
5336
  top 4
5042
5337
  hexpand true
5043
5338
  vexpand true
5044
5339
  halign :fill
5045
5340
  valign :fill
5046
-
5047
- @area = area {
5048
- on_draw do |area_draw_params|
5341
+
5342
+ on_draw do |area_draw_params|
5343
+ path {
5344
+ rectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT)
5345
+
5346
+ fill :white
5347
+ }
5348
+
5349
+ @shapes_data.each do |shape_data|
5049
5350
  path {
5050
- rectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT)
5051
-
5052
- fill :white
5351
+ shape_data[:shape] = send(SHAPES.sample, *shape_data[:args])
5352
+
5353
+ fill shape_data[:fill]
5354
+ stroke shape_data[:stroke]
5053
5355
  }
5054
-
5055
- @circles_data.each do |circle_data|
5056
- path {
5057
- circle_data[:circle] = circle(*circle_data[:args])
5058
-
5059
- fill circle_data[:fill]
5060
- stroke circle_data[:stroke]
5061
- }
5062
- end
5063
5356
  end
5064
-
5065
- on_mouse_down do |area_mouse_event|
5066
- clicked_circle_data = @circles_data.find do |circle_data|
5067
- circle_data[:fill].nil? && circle_data[:circle].include?(area_mouse_event[:x], area_mouse_event[:y])
5068
- end
5069
- if clicked_circle_data
5070
- clicked_circle_data[:fill] = clicked_circle_data[:stroke]
5071
- @area.queue_redraw_all
5072
- self.score += 1 # notifies score observers automatically of change
5073
- end
5074
- end
5075
- }
5357
+ end
5358
+
5359
+ on_mouse_down do |area_mouse_event|
5360
+ color_shape(area_mouse_event[:x], area_mouse_event[:y])
5361
+ end
5076
5362
  }
5077
-
5078
5363
  }
5079
5364
  }.show
5080
5365
  end
5081
5366
  end
5082
5367
 
5083
- ColorTheCircles.new.launch
5368
+ ColorTheShapes.new.launch
5084
5369
  ```
5085
5370
 
5086
5371
  ### Basic Draw Text
@@ -5578,7 +5863,132 @@ class CustomDrawText
5578
5863
  end
5579
5864
 
5580
5865
  CustomDrawText.new.launch
5866
+ ```
5867
+
5868
+ ### Method-Based Custom Keyword
5869
+
5870
+ [examples/method_based_custom_keyword.rb](examples/method_based_custom_keyword.rb)
5871
+
5872
+ Run with this command from the root of the project if you cloned the project:
5873
+
5874
+ ```
5875
+ ruby -r './lib/glimmer-dsl-libui' examples/method_based_custom_keyword.rb
5876
+ ```
5877
+
5878
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
5879
+
5880
+ ```
5881
+ ruby -r glimmer-dsl-libui -e "require 'examples/method_based_custom_keyword'"
5882
+ ```
5883
+
5884
+ Mac
5885
+
5886
+ ![glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png)
5887
+
5888
+ Linux
5889
+
5890
+ ![glimmer-dsl-libui-linux-method-based-custom-keyword.png](images/glimmer-dsl-libui-linux-method-based-custom-keyword.png)
5891
+
5892
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
5893
+
5894
+ ```ruby
5895
+ require 'glimmer-dsl-libui'
5896
+ require 'facets'
5897
+
5898
+ include Glimmer
5899
+
5900
+ Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
5901
+
5902
+ def form_field(model, property)
5903
+ property = property.to_s
5904
+ entry { |e|
5905
+ label property.underscore.split('_').map(&:capitalize).join(' ')
5906
+ text model.send(property).to_s
5907
+
5908
+ on_changed do
5909
+ model.send("#{property}=", e.text)
5910
+ end
5911
+ }
5912
+ end
5913
+
5914
+ def address_form(address)
5915
+ form {
5916
+ form_field(address, :street)
5917
+ form_field(address, :p_o_box)
5918
+ form_field(address, :city)
5919
+ form_field(address, :state)
5920
+ form_field(address, :zip_code)
5921
+ }
5922
+ end
5923
+
5924
+ def label_pair(model, attribute, value)
5925
+ name_label = nil
5926
+ value_label = nil
5927
+ horizontal_box {
5928
+ name_label = label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
5929
+ value_label = label(value.to_s)
5930
+ }
5931
+ Glimmer::DataBinding::Observer.proc do
5932
+ value_label.text = model.send(attribute)
5933
+ end.observe(model, attribute)
5934
+ end
5935
+
5936
+ def address(address)
5937
+ vertical_box {
5938
+ address.each_pair do |attribute, value|
5939
+ label_pair(address, attribute, value)
5940
+ end
5941
+ }
5942
+ end
5943
+
5944
+ address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
5945
+ address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
5581
5946
 
5947
+ window('Method-Based Custom Keyword') {
5948
+ margined true
5949
+
5950
+ horizontal_box {
5951
+ vertical_box {
5952
+ label('Address 1') {
5953
+ stretchy false
5954
+ }
5955
+
5956
+ address_form(address1)
5957
+
5958
+ horizontal_separator {
5959
+ stretchy false
5960
+ }
5961
+
5962
+ label('Address 1 (Saved)') {
5963
+ stretchy false
5964
+ }
5965
+
5966
+ address(address1)
5967
+ }
5968
+
5969
+ vertical_separator {
5970
+ stretchy false
5971
+ }
5972
+
5973
+ vertical_box {
5974
+ label('Address 2') {
5975
+ stretchy false
5976
+ }
5977
+
5978
+ address_form(address2)
5979
+
5980
+ horizontal_separator {
5981
+ stretchy false
5982
+ }
5983
+
5984
+ label('Address 2 (Saved)') {
5985
+ stretchy false
5986
+ }
5987
+
5988
+ address(address2)
5989
+ }
5990
+ }
5991
+ }.show
5582
5992
  ```
5583
5993
 
5584
5994
  ## Contributing to glimmer-dsl-libui