glimmer-dsl-libui 0.2.7 → 0.2.11

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,4 +1,4 @@
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.7
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.11
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
4
  [![Maintainability](https://api.codeclimate.com/v1/badges/ce2853efdbecf6ebdc73/maintainability)](https://codeclimate.com/github/AndyObtiva/glimmer-dsl-libui/maintainability)
@@ -10,14 +10,16 @@
10
10
 
11
11
  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
12
 
13
+ **(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)**
14
+
13
15
  [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
16
  - Declarative DSL syntax that visually maps to the GUI control hierarchy
15
17
  - Convention over configuration via smart defaults and automation of low-level details
16
18
  - 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
19
  - Custom Control support
19
- - Scaffolding for new custom controls, apps, and gems
20
- - Native-Executable packaging on Mac, Windows, and Linux.
20
+ - [Far Future Plan] Bidirectional Data-Binding to declaratively wire and automatically synchronize GUI with Business Models
21
+ - [Far Future Plan] Scaffolding for new custom controls, apps, and gems
22
+ - [Far Future Plan] Native-Executable packaging on Mac, Windows, and Linux.
21
23
 
22
24
  Hello, World!
23
25
 
@@ -61,7 +63,7 @@ window('Task Progress', 300, 200) {
61
63
 
62
64
  on_clicked do
63
65
  data.each_with_index do |row_data, row|
64
- data[row] = [row_data[0], 100] # automatically updates table due to implicit data-binding
66
+ data[row][1] = 100 # automatically updates table due to implicit data-binding
65
67
  end
66
68
  end
67
69
  }
@@ -84,14 +86,15 @@ window('Area Gallery', 400, 400) {
84
86
  path { # declarative stable path
85
87
  square(0, 0, 100)
86
88
  square(100, 100, 400)
87
-
89
+
88
90
  fill r: 102, g: 102, b: 204
89
91
  }
90
92
  path { # declarative stable path
91
93
  rectangle(0, 100, 100, 400)
92
94
  rectangle(100, 0, 400, 100)
93
-
94
- fill r: 204, g: 102, b: 204
95
+
96
+ # linear gradient (has x0, y0, x1, y1, and stops)
97
+ 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
98
  }
96
99
  path { # declarative stable path
97
100
  figure(100, 100) {
@@ -117,17 +120,26 @@ window('Area Gallery', 400, 400) {
117
120
  fill r: 202, g: 102, b: 204, a: 0.5
118
121
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
119
122
  }
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
+ }
120
130
  path { # declarative stable path
121
131
  circle(200, 200, 90)
122
132
 
123
133
  fill r: 202, g: 102, b: 204, a: 0.5
124
134
  stroke r: 0, g: 0, b: 0, thickness: 2
125
135
  }
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
136
+ text(160, 40, 100) { # x, y, width
137
+ string {
138
+ font family: 'Times', size: 14
139
+ color :black
140
+
141
+ 'Area Gallery'
142
+ }
131
143
  }
132
144
 
133
145
  on_mouse_event do |area_mouse_event|
@@ -186,7 +198,7 @@ window('Area Gallery', 400, 400) {
186
198
 
187
199
  [Check Out Many More Examples Over Here!](#examples)
188
200
 
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.
201
+ 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
202
 
191
203
  Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interested in:
192
204
  - [glimmer-dsl-swt](https://github.com/AndyObtiva/glimmer-dsl-swt): Glimmer DSL for SWT (JRuby Desktop Development GUI Framework)
@@ -197,7 +209,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
197
209
 
198
210
  ## Table of Contents
199
211
 
200
- - [Glimmer DSL for LibUI 0.2.7](#-glimmer-dsl-for-libui-027)
212
+ - [Glimmer DSL for LibUI](#)
201
213
  - [Glimmer GUI DSL Concepts](#glimmer-gui-dsl-concepts)
202
214
  - [Usage](#usage)
203
215
  - [Girb (Glimmer IRB)](#girb-glimmer-irb)
@@ -211,8 +223,10 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
211
223
  - [Table API](#table-api)
212
224
  - [Area API](#area-api)
213
225
  - [Smart Defaults and Conventions](#smart-defaults-and-conventions)
226
+ - [Custom Keywords](#custom-keywords)
214
227
  - [API Gotchas](#api-gotchas)
215
228
  - [Original API](#original-api)
229
+ - [Packaging](#packaging)
216
230
  - [Glimmer Style Guide](#glimmer-style-guide)
217
231
  - [Examples](#examples)
218
232
  - [Basic Window](#basic-window)
@@ -247,6 +261,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
247
261
  - [Color The Circles](#color-the-circles)
248
262
  - [Basic Draw Text](#basic-draw-text)
249
263
  - [Custom Draw Text](#custom-draw-text)
264
+ - [Method-Based Custom Keyword](#method-based-custom-keyword)
250
265
  - [Contributing to glimmer-dsl-libui](#contributing-to-glimmer-dsl-libui)
251
266
  - [Help](#help)
252
267
  - [Issues](#issues)
@@ -334,7 +349,7 @@ gem install glimmer-dsl-libui
334
349
  Or install via Bundler `Gemfile`:
335
350
 
336
351
  ```ruby
337
- gem 'glimmer-dsl-libui', '~> 0.2.7'
352
+ gem 'glimmer-dsl-libui', '~> 0.2.11'
338
353
  ```
339
354
 
340
355
  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 +461,7 @@ Control(Args) | Properties | Listeners
446
461
  `msg_box_error(window = main_window as Glimmer::LibUI::WindowProxy, title as String, description as String)` | None | None
447
462
  `non_wrapping_multiline_entry` | `read_only` (Boolean), `text` (`String`) | `on_changed`
448
463
  `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
464
+ `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
465
  `preferences_menu_item` | None | `on_clicked`
451
466
  `progress_bar` | `value` (`Numeric`) | None
452
467
  `progress_bar_column(name as String)` | None | None
@@ -457,10 +472,10 @@ Control(Args) | Properties | Listeners
457
472
  `slider(min as Numeric, max as Numeric)` | `value` (`Numeric`) | `on_changed`
458
473
  `spinbox(min as Numeric, max as Numeric)` | `value` (`Numeric`) | `on_changed`
459
474
  `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
475
+ `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` | None
461
476
  `tab` | `margined` (Boolean), `num_pages` (`Integer`) | None
462
477
  `tab_item(name as String)` | `index` [read-only] (`Integer`), `margined` (Boolean), `name` [read-only] (`String`) | None
463
- `table` | `cell_rows` (`Array` (rows) of `Arrays` (row columns) of cell values (e.g. `String` values for `text_column` cells or `Array` of `image`/`String` for `image_text_column`)), `editable` as Boolean | None
478
+ `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| ...}`
464
479
  `text(x = 0 as Numeric, y = 0 as Numeric, width = area_width as Numeric)` | `align`, `default_font` | None
465
480
  `text_column(name as String)` | `editable` (Boolean) | None
466
481
  `text_color_column(name as String)` | `editable` (Boolean) | None
@@ -813,7 +828,7 @@ To draw `text` in an `area`, you simply nest a `text(x, y, width)` control direc
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.
817
832
 
818
833
  Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
819
834
 
@@ -882,6 +897,114 @@ window('area text drawing') {
882
897
  - 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
898
  - Color alpha value defaults to `1.0` when not specified.
884
899
 
900
+ ### Custom Keywords
901
+
902
+ 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.
903
+
904
+ Example that defines `form_field`, `address_form`, `label_pair`, and `address` keywords (you may copy/paste in [`girb`](#girb-glimmer-irb)):
905
+
906
+ ```ruby
907
+ require 'glimmer-dsl-libui'
908
+ require 'facets'
909
+
910
+ include Glimmer
911
+
912
+ Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
913
+
914
+ def form_field(model, property)
915
+ property = property.to_s
916
+ entry { |e|
917
+ label property.underscore.split('_').map(&:capitalize).join(' ')
918
+ text model.send(property).to_s
919
+
920
+ on_changed do
921
+ model.send("#{property}=", e.text)
922
+ end
923
+ }
924
+ end
925
+
926
+ def address_form(address)
927
+ form {
928
+ form_field(address, :street)
929
+ form_field(address, :p_o_box)
930
+ form_field(address, :city)
931
+ form_field(address, :state)
932
+ form_field(address, :zip_code)
933
+ }
934
+ end
935
+
936
+ def label_pair(model, attribute, value)
937
+ name_label = nil
938
+ value_label = nil
939
+ horizontal_box {
940
+ name_label = label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
941
+ value_label = label(value.to_s)
942
+ }
943
+ Glimmer::DataBinding::Observer.proc do
944
+ value_label.text = model.send(attribute)
945
+ end.observe(model, attribute)
946
+ end
947
+
948
+ def address(address)
949
+ vertical_box {
950
+ address.each_pair do |attribute, value|
951
+ label_pair(address, attribute, value)
952
+ end
953
+ }
954
+ end
955
+
956
+ address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
957
+ address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
958
+
959
+ window('Method-Based Custom Keyword') {
960
+ margined true
961
+
962
+ horizontal_box {
963
+ vertical_box {
964
+ label('Address 1') {
965
+ stretchy false
966
+ }
967
+
968
+ address_form(address1)
969
+
970
+ horizontal_separator {
971
+ stretchy false
972
+ }
973
+
974
+ label('Address 1 (Saved)') {
975
+ stretchy false
976
+ }
977
+
978
+ address(address1)
979
+ }
980
+
981
+ vertical_separator {
982
+ stretchy false
983
+ }
984
+
985
+ vertical_box {
986
+ label('Address 2') {
987
+ stretchy false
988
+ }
989
+
990
+ address_form(address2)
991
+
992
+ horizontal_separator {
993
+ stretchy false
994
+ }
995
+
996
+ label('Address 2 (Saved)') {
997
+ stretchy false
998
+ }
999
+
1000
+ address(address2)
1001
+ }
1002
+ }
1003
+ }.show
1004
+ ```
1005
+
1006
+ ![glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png)
1007
+
885
1008
  ### API Gotchas
886
1009
 
887
1010
  - 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 +1013,20 @@ window('area text drawing') {
890
1013
 
891
1014
  ### Original API
892
1015
 
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)
1016
+ 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):
1017
+ - Check out [LibUI ffi.rb](https://github.com/kojix2/LibUI/blob/main/lib/libui/ffi.rb)
1018
+ - Check out the [libui C headers](https://github.com/andlabs/libui/blob/master/ui.h)
1019
+ - 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.
1020
+
1021
+ ## Packaging
1022
+
1023
+ 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.
1024
+
1025
+ 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.
1026
+
1027
+ For Mac, consider [Platybus](https://github.com/sveinbjornt/Platypus) (builds a native Mac app from a Ruby script)
1028
+
1029
+ 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
1030
 
896
1031
  ## Glimmer Style Guide
897
1032
 
@@ -908,8 +1043,6 @@ The following examples include reimplementions of the examples in the [LibUI](ht
908
1043
 
909
1044
  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
1045
 
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
1046
  [examples/meta_example.rb](examples/meta_example.rb)
914
1047
 
915
1048
  Run with this command from the root of the project if you cloned the project:
@@ -941,59 +1074,122 @@ require 'facets'
941
1074
  class MetaExample
942
1075
  include Glimmer
943
1076
 
1077
+ def initialize
1078
+ @selected_example_index = 0
1079
+ end
1080
+
944
1081
  def examples
945
1082
  if @examples.nil?
946
1083
  example_files = Dir.glob(File.join(File.expand_path('.', __dir__), '**', '*.rb'))
947
1084
  example_file_names = example_files.map { |f| File.basename(f, '.rb') }
948
- example_file_names = example_file_names.reject { |f| f == 'meta_example' }
1085
+ example_file_names = example_file_names.reject { |f| f == 'meta_example' || f.match(/\d$/) }
949
1086
  @examples = example_file_names.map { |f| f.underscore.titlecase }
950
1087
  end
951
1088
  @examples
952
1089
  end
953
1090
 
1091
+ def examples_with_versions
1092
+ examples.map do |example|
1093
+ version_count_for(example) > 1 ? "#{example} (#{version_count_for(example)} versions)" : example
1094
+ end
1095
+ end
1096
+
954
1097
  def file_path_for(example)
955
1098
  File.join(File.expand_path('.', __dir__), "#{example.underscore}.rb")
956
1099
  end
957
1100
 
1101
+ def version_count_for(example)
1102
+ Dir.glob(File.join(File.expand_path('.', __dir__), "#{example.underscore}*.rb")).select {|file| file.match(/\d\.rb$/)}.count + 1
1103
+ end
1104
+
958
1105
  def glimmer_dsl_libui_file
959
1106
  File.expand_path('../lib/glimmer-dsl-libui', __dir__)
960
1107
  end
961
1108
 
1109
+ def selected_example
1110
+ examples[@selected_example_index]
1111
+ end
1112
+
1113
+ def run_example(example)
1114
+ command = "ruby -r #{glimmer_dsl_libui_file} #{example} 2>&1"
1115
+ result = ''
1116
+ IO.popen(command) do |f|
1117
+ f.each_line do |line|
1118
+ result << line
1119
+ puts line
1120
+ end
1121
+ end
1122
+ msg_box('Error Running Example', result) if result.downcase.include?('error')
1123
+ end
1124
+
962
1125
  def launch
963
1126
  window('Meta-Example', 700, 500) {
964
1127
  margined true
965
1128
 
966
1129
  horizontal_box {
967
1130
  vertical_box {
968
- @rbs = radio_buttons {
1131
+ stretchy false
1132
+
1133
+ @example_radio_buttons = radio_buttons {
969
1134
  stretchy false
970
- items examples
971
- selected 0
1135
+ items examples_with_versions
1136
+ selected @selected_example_index
972
1137
 
973
1138
  on_selected do
974
- @nwme.text = File.read(file_path_for(@examples[@rbs.selected]))
1139
+ @selected_example_index = @example_radio_buttons.selected
1140
+ example = selected_example
1141
+ @code_entry.text = File.read(file_path_for(example))
1142
+ @version_spinbox.value = 1
975
1143
  end
976
1144
  }
977
- button('Launch') {
1145
+
1146
+ horizontal_box {
1147
+ label('Version') {
1148
+ stretchy false
1149
+ }
1150
+
1151
+ @version_spinbox = spinbox(1, 100) {
1152
+ value 1
1153
+
1154
+ on_changed do
1155
+ example = selected_example
1156
+ if @version_spinbox.value > version_count_for(example)
1157
+ @version_spinbox.value -= 1
1158
+ else
1159
+ version_number = @version_spinbox.value == 1 ? '' : @version_spinbox.value
1160
+ example = "#{selected_example}#{version_number}"
1161
+ @code_entry.text = File.read(file_path_for(example))
1162
+ end
1163
+ end
1164
+ }
1165
+ }
1166
+
1167
+ horizontal_box {
978
1168
  stretchy false
979
1169
 
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])}"
1170
+ button('Launch') {
1171
+ on_clicked do
1172
+ begin
1173
+ meta_example_file = File.join(Dir.home, '.meta_example.rb')
1174
+ File.write(meta_example_file, @code_entry.text)
1175
+ run_example(meta_example_file)
1176
+ rescue => e
1177
+ puts e.full_message
1178
+ puts 'Unable to write code changes! Running original example...'
1179
+ run_example(file_path_for(selected_example))
1180
+ end
989
1181
  end
990
- end
1182
+ }
1183
+ button('Reset') {
1184
+ on_clicked do
1185
+ @code_entry.text = File.read(file_path_for(selected_example))
1186
+ end
1187
+ }
991
1188
  }
992
1189
  }
993
- vertical_box {
994
- @nwme = non_wrapping_multiline_entry {
995
- text File.read(file_path_for(@examples[@rbs.selected]))
996
- }
1190
+
1191
+ @code_entry = non_wrapping_multiline_entry {
1192
+ text File.read(file_path_for(selected_example))
997
1193
  }
998
1194
  }
999
1195
  }.show
@@ -2060,6 +2256,8 @@ include Glimmer
2060
2256
 
2061
2257
  window('color button', 230) {
2062
2258
  color_button { |cb|
2259
+ color :blue
2260
+
2063
2261
  on_changed do
2064
2262
  rgba = cb.color
2065
2263
  p rgba
@@ -2342,11 +2540,21 @@ window('Form') {
2342
2540
  @last_name_entry = entry {
2343
2541
  label 'Last Name' # label property is available when control is nested under form
2344
2542
  }
2543
+
2544
+ @phone_entry = entry {
2545
+ label 'Phone' # label property is available when control is nested under form
2546
+ }
2547
+
2548
+ @email_entry = entry {
2549
+ label 'Email' # label property is available when control is nested under form
2550
+ }
2345
2551
  }
2346
2552
 
2347
- button('Display Name') {
2553
+ button('Display Info') {
2554
+ stretchy false
2555
+
2348
2556
  on_clicked do
2349
- msg_box('Name', "#{@first_name_entry.text} #{@last_name_entry.text}")
2557
+ msg_box('Info', "#{@first_name_entry.text} #{@last_name_entry.text} has phone #{@phone_entry.text} and email #{@email_entry.text}")
2350
2558
  end
2351
2559
  }
2352
2560
  }
@@ -2524,6 +2732,14 @@ window('Editable animal sounds', 300, 200) {
2524
2732
 
2525
2733
  cell_rows data
2526
2734
  editable true
2735
+
2736
+ on_changed do |row, type, row_data| # fires on all changes (even ones happening through data array)
2737
+ puts "Row #{row} #{type}: #{row_data}"
2738
+ end
2739
+
2740
+ on_edited do |row, row_data| # only fires on direct table editing
2741
+ puts "Row #{row} edited: #{row_data}"
2742
+ end
2527
2743
  }
2528
2744
  }
2529
2745
 
@@ -2882,6 +3098,10 @@ window('Animal sounds', 300, 200) {
2882
3098
  }
2883
3099
 
2884
3100
  cell_rows data # implicit data-binding
3101
+
3102
+ on_changed do |row, type, row_data|
3103
+ puts "Row #{row} #{type}: #{row_data}"
3104
+ end
2885
3105
  }
2886
3106
  }
2887
3107
  }.show
@@ -3044,7 +3264,7 @@ window('Task Progress', 300, 200) {
3044
3264
 
3045
3265
  on_clicked do
3046
3266
  data.each_with_index do |row_data, row|
3047
- data[row] = [row_data[0], 100] # automatically updates table due to implicit data-binding
3267
+ data[row][1] = 100 # automatically updates table due to implicit data-binding
3048
3268
  end
3049
3269
  end
3050
3270
  }
@@ -3239,6 +3459,10 @@ window('Contacts', 600, 600) { |w|
3239
3459
  text_column('State')
3240
3460
 
3241
3461
  cell_rows data # implicit data-binding
3462
+
3463
+ on_changed do |row, type, row_data|
3464
+ puts "Row #{row} #{type}: #{row_data}"
3465
+ end
3242
3466
  }
3243
3467
  }
3244
3468
  }.show
@@ -3634,14 +3858,15 @@ window('Area Gallery', 400, 400) {
3634
3858
  path { # declarative stable path
3635
3859
  square(0, 0, 100)
3636
3860
  square(100, 100, 400)
3637
-
3861
+
3638
3862
  fill r: 102, g: 102, b: 204
3639
3863
  }
3640
3864
  path { # declarative stable path
3641
3865
  rectangle(0, 100, 100, 400)
3642
3866
  rectangle(100, 0, 400, 100)
3643
-
3644
- fill r: 204, g: 102, b: 204
3867
+
3868
+ # linear gradient (has x0, y0, x1, y1, and stops)
3869
+ 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}]
3645
3870
  }
3646
3871
  path { # declarative stable path
3647
3872
  figure(100, 100) {
@@ -3667,17 +3892,26 @@ window('Area Gallery', 400, 400) {
3667
3892
  fill r: 202, g: 102, b: 204, a: 0.5
3668
3893
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3669
3894
  }
3895
+ path { # declarative stable path
3896
+ arc(400, 220, 180, 90, 90, false)
3897
+
3898
+ # radial gradient (has an outer_radius in addition to x0, y0, x1, y1, and stops)
3899
+ 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}]
3900
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3901
+ }
3670
3902
  path { # declarative stable path
3671
3903
  circle(200, 200, 90)
3672
3904
 
3673
3905
  fill r: 202, g: 102, b: 204, a: 0.5
3674
3906
  stroke r: 0, g: 0, b: 0, thickness: 2
3675
3907
  }
3676
- path { # declarative stable path
3677
- arc(400, 220, 180, 90, 90, false)
3678
-
3679
- fill r: 204, g: 102, b: 204, a: 0.5
3680
- stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3908
+ text(160, 40, 100) { # x, y, width
3909
+ string {
3910
+ font family: 'Times', size: 14
3911
+ color :black
3912
+
3913
+ 'Area Gallery'
3914
+ }
3681
3915
  }
3682
3916
 
3683
3917
  on_mouse_event do |area_mouse_event|
@@ -3768,7 +4002,8 @@ window('Area Gallery', 400, 400) {
3768
4002
  height 100
3769
4003
  }
3770
4004
 
3771
- fill r: 204, g: 102, b: 204
4005
+ # linear gradient (has x0, y0, x1, y1, and stops)
4006
+ 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}]
3772
4007
  }
3773
4008
  path { # declarative stable path
3774
4009
  figure {
@@ -3830,16 +4065,6 @@ window('Area Gallery', 400, 400) {
3830
4065
  fill r: 202, g: 102, b: 204, a: 0.5
3831
4066
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3832
4067
  }
3833
- path { # declarative stable path
3834
- circle {
3835
- x_center 200
3836
- y_center 200
3837
- radius 90
3838
- }
3839
-
3840
- fill r: 202, g: 102, b: 204, a: 0.5
3841
- stroke r: 0, g: 0, b: 0, thickness: 2
3842
- }
3843
4068
  path { # declarative stable path
3844
4069
  arc {
3845
4070
  x_center 400
@@ -3850,9 +4075,32 @@ window('Area Gallery', 400, 400) {
3850
4075
  is_negative false
3851
4076
  }
3852
4077
 
3853
- fill r: 204, g: 102, b: 204, a: 0.5
4078
+ # radial gradient (has an outer_radius in addition to x0, y0, x1, y1, and stops)
4079
+ 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}]
3854
4080
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3855
4081
  }
4082
+ path { # declarative stable path
4083
+ circle {
4084
+ x_center 200
4085
+ y_center 200
4086
+ radius 90
4087
+ }
4088
+
4089
+ fill r: 202, g: 102, b: 204, a: 0.5
4090
+ stroke r: 0, g: 0, b: 0, thickness: 2
4091
+ }
4092
+ text {
4093
+ x 160
4094
+ y 40
4095
+ width 100
4096
+
4097
+ string {
4098
+ font family: 'Times', size: 14
4099
+ color :black
4100
+
4101
+ 'Area Gallery'
4102
+ }
4103
+ }
3856
4104
 
3857
4105
  on_mouse_event do |area_mouse_event|
3858
4106
  p area_mouse_event
@@ -3925,7 +4173,8 @@ window('Area Gallery', 400, 400) {
3925
4173
  rectangle(0, 100, 100, 400)
3926
4174
  rectangle(100, 0, 400, 100)
3927
4175
 
3928
- fill r: 204, g: 102, b: 204
4176
+ # linear gradient (has x0, y0, x1, y1, and stops)
4177
+ 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}]
3929
4178
  }
3930
4179
  path { # a dynamic path is added semi-declaratively inside on_draw block
3931
4180
  figure(100, 100) {
@@ -3951,17 +4200,26 @@ window('Area Gallery', 400, 400) {
3951
4200
  fill r: 202, g: 102, b: 204, a: 0.5
3952
4201
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3953
4202
  }
4203
+ path { # a dynamic path is added semi-declaratively inside on_draw block
4204
+ arc(400, 220, 180, 90, 90, false)
4205
+
4206
+ # radial gradient (has an outer_radius in addition to x0, y0, x1, y1, and stops)
4207
+ 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}]
4208
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
4209
+ }
3954
4210
  path { # a dynamic path is added semi-declaratively inside on_draw block
3955
4211
  circle(200, 200, 90)
3956
4212
 
3957
4213
  fill r: 202, g: 102, b: 204, a: 0.5
3958
4214
  stroke r: 0, g: 0, b: 0, thickness: 2
3959
4215
  }
3960
- path { # a dynamic path is added semi-declaratively inside on_draw block
3961
- arc(400, 220, 180, 90, 90, false)
3962
-
3963
- fill r: 204, g: 102, b: 204, a: 0.5
3964
- stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
4216
+ text(160, 40, 100) { # x, y, width
4217
+ string {
4218
+ font family: 'Times', size: 14
4219
+ color :black
4220
+
4221
+ 'Area Gallery'
4222
+ }
3965
4223
  }
3966
4224
  end
3967
4225
 
@@ -4054,7 +4312,8 @@ window('Area Gallery', 400, 400) {
4054
4312
  height 100
4055
4313
  }
4056
4314
 
4057
- fill r: 204, g: 102, b: 204
4315
+ # linear gradient (has x0, y0, x1, y1, and stops)
4316
+ 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}]
4058
4317
  }
4059
4318
  path { # a dynamic path is added semi-declaratively inside on_draw block
4060
4319
  figure {
@@ -4116,16 +4375,6 @@ window('Area Gallery', 400, 400) {
4116
4375
  fill r: 202, g: 102, b: 204, a: 0.5
4117
4376
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
4118
4377
  }
4119
- path { # a dynamic path is added semi-declaratively inside on_draw block
4120
- circle {
4121
- x_center 200
4122
- y_center 200
4123
- radius 90
4124
- }
4125
-
4126
- fill r: 202, g: 102, b: 204, a: 0.5
4127
- stroke r: 0, g: 0, b: 0, thickness: 2
4128
- }
4129
4378
  path { # a dynamic path is added semi-declaratively inside on_draw block
4130
4379
  arc {
4131
4380
  x_center 400
@@ -4136,9 +4385,32 @@ window('Area Gallery', 400, 400) {
4136
4385
  is_negative false
4137
4386
  }
4138
4387
 
4139
- fill r: 204, g: 102, b: 204, a: 0.5
4388
+ # radial gradient (has an outer_radius in addition to x0, y0, x1, y1, and stops)
4389
+ 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}]
4140
4390
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
4141
4391
  }
4392
+ path { # a dynamic path is added semi-declaratively inside on_draw block
4393
+ circle {
4394
+ x_center 200
4395
+ y_center 200
4396
+ radius 90
4397
+ }
4398
+
4399
+ fill r: 202, g: 102, b: 204, a: 0.5
4400
+ stroke r: 0, g: 0, b: 0, thickness: 2
4401
+ }
4402
+ text {
4403
+ x 160
4404
+ y 40
4405
+ width 100
4406
+
4407
+ string {
4408
+ font family: 'Times', size: 14
4409
+ color :black
4410
+
4411
+ 'Area Gallery'
4412
+ }
4413
+ }
4142
4414
  end
4143
4415
 
4144
4416
  on_mouse_event do |area_mouse_event|
@@ -5562,7 +5834,132 @@ class CustomDrawText
5562
5834
  end
5563
5835
 
5564
5836
  CustomDrawText.new.launch
5837
+ ```
5838
+
5839
+ ### Method-Based Custom Keyword
5840
+
5841
+ [examples/method_based_custom_keyword.rb](examples/method_based_custom_keyword.rb)
5842
+
5843
+ Run with this command from the root of the project if you cloned the project:
5565
5844
 
5845
+ ```
5846
+ ruby -r './lib/glimmer-dsl-libui' examples/method_based_custom_keyword.rb
5847
+ ```
5848
+
5849
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
5850
+
5851
+ ```
5852
+ ruby -r glimmer-dsl-libui -e "require 'examples/method_based_custom_keyword'"
5853
+ ```
5854
+
5855
+ Mac
5856
+
5857
+ ![glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png)
5858
+
5859
+ Linux
5860
+
5861
+ ![glimmer-dsl-libui-linux-method-based-custom-keyword.png](images/glimmer-dsl-libui-linux-method-based-custom-keyword.png)
5862
+
5863
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
5864
+
5865
+ ```ruby
5866
+ require 'glimmer-dsl-libui'
5867
+ require 'facets'
5868
+
5869
+ include Glimmer
5870
+
5871
+ Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
5872
+
5873
+ def form_field(model, property)
5874
+ property = property.to_s
5875
+ entry { |e|
5876
+ label property.underscore.split('_').map(&:capitalize).join(' ')
5877
+ text model.send(property).to_s
5878
+
5879
+ on_changed do
5880
+ model.send("#{property}=", e.text)
5881
+ end
5882
+ }
5883
+ end
5884
+
5885
+ def address_form(address)
5886
+ form {
5887
+ form_field(address, :street)
5888
+ form_field(address, :p_o_box)
5889
+ form_field(address, :city)
5890
+ form_field(address, :state)
5891
+ form_field(address, :zip_code)
5892
+ }
5893
+ end
5894
+
5895
+ def label_pair(model, attribute, value)
5896
+ name_label = nil
5897
+ value_label = nil
5898
+ horizontal_box {
5899
+ name_label = label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
5900
+ value_label = label(value.to_s)
5901
+ }
5902
+ Glimmer::DataBinding::Observer.proc do
5903
+ value_label.text = model.send(attribute)
5904
+ end.observe(model, attribute)
5905
+ end
5906
+
5907
+ def address(address)
5908
+ vertical_box {
5909
+ address.each_pair do |attribute, value|
5910
+ label_pair(address, attribute, value)
5911
+ end
5912
+ }
5913
+ end
5914
+
5915
+ address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
5916
+ address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
5917
+
5918
+ window('Method-Based Custom Keyword') {
5919
+ margined true
5920
+
5921
+ horizontal_box {
5922
+ vertical_box {
5923
+ label('Address 1') {
5924
+ stretchy false
5925
+ }
5926
+
5927
+ address_form(address1)
5928
+
5929
+ horizontal_separator {
5930
+ stretchy false
5931
+ }
5932
+
5933
+ label('Address 1 (Saved)') {
5934
+ stretchy false
5935
+ }
5936
+
5937
+ address(address1)
5938
+ }
5939
+
5940
+ vertical_separator {
5941
+ stretchy false
5942
+ }
5943
+
5944
+ vertical_box {
5945
+ label('Address 2') {
5946
+ stretchy false
5947
+ }
5948
+
5949
+ address_form(address2)
5950
+
5951
+ horizontal_separator {
5952
+ stretchy false
5953
+ }
5954
+
5955
+ label('Address 2 (Saved)') {
5956
+ stretchy false
5957
+ }
5958
+
5959
+ address(address2)
5960
+ }
5961
+ }
5962
+ }.show
5566
5963
  ```
5567
5964
 
5568
5965
  ## Contributing to glimmer-dsl-libui