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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -0
- data/README.md +599 -189
- data/VERSION +1 -1
- data/bin/girb +0 -0
- data/bin/girb_runner.rb +1 -1
- data/examples/area_gallery.rb +24 -14
- data/examples/area_gallery2.rb +37 -21
- data/examples/area_gallery3.rb +22 -12
- data/examples/area_gallery4.rb +37 -21
- data/examples/basic_draw_text2.rb +0 -1
- data/examples/basic_table_button.rb +2 -1
- data/examples/basic_table_checkbox.rb +1 -1
- data/examples/basic_table_image.rb +1 -0
- data/examples/basic_table_image_text.rb +1 -0
- data/examples/color_button.rb +2 -0
- data/examples/{color_the_circles.rb → color_the_shapes.rb} +45 -44
- data/examples/control_gallery.rb +6 -0
- data/examples/editable_table.rb +2 -0
- data/examples/form.rb +12 -2
- data/examples/meta_example.rb +34 -18
- data/examples/method_based_custom_keyword.rb +97 -0
- data/glimmer-dsl-libui.gemspec +0 -0
- data/lib/glimmer/dsl/libui/string_expression.rb +9 -2
- data/lib/glimmer/libui/attributed_string.rb +32 -12
- data/lib/glimmer/libui/control_proxy/path_proxy.rb +31 -6
- data/lib/glimmer/libui/control_proxy/table_proxy.rb +30 -10
- data/lib/glimmer/libui.rb +1 -1
- metadata +13 -9
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.
|
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
|
-
-
|
20
|
-
-
|
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
|
-
|
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
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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
|
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
|
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
|
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.
|
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
|
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`
|
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`
|
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`:
|
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
|
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
|
-
|
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
|
-
|
1140
|
+
stretchy false
|
1141
|
+
|
1142
|
+
@example_radio_buttons = radio_buttons {
|
969
1143
|
stretchy false
|
970
|
-
items
|
971
|
-
selected
|
1144
|
+
items examples_with_versions
|
1145
|
+
selected @selected_example_index
|
972
1146
|
|
973
1147
|
on_selected do
|
974
|
-
@
|
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
|
-
|
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
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
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
|
-
|
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
|
-
|
994
|
-
|
995
|
-
|
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
|
2562
|
+
button('Display Info') {
|
2563
|
+
stretchy false
|
2564
|
+
|
2348
2565
|
on_clicked do
|
2349
|
-
msg_box('
|
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
|
-
|
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
|
-
|
3687
|
-
|
3688
|
-
|
3689
|
-
|
3690
|
-
|
3691
|
-
|
3692
|
-
|
3693
|
-
|
3694
|
-
|
3695
|
-
|
3696
|
-
|
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
|
-
|
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
|
-
|
3850
|
-
|
3851
|
-
|
3852
|
-
|
3853
|
-
|
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
|
-
|
3857
|
-
|
3858
|
-
|
3859
|
-
|
3860
|
-
|
3861
|
-
|
3862
|
-
|
3863
|
-
|
3864
|
-
|
3865
|
-
|
3866
|
-
|
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
|
-
|
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
|
-
|
3971
|
-
|
3972
|
-
|
3973
|
-
|
3974
|
-
|
3975
|
-
|
3976
|
-
|
3977
|
-
|
3978
|
-
|
3979
|
-
|
3980
|
-
|
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
|
-
|
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
|
-
|
4136
|
-
|
4137
|
-
|
4138
|
-
|
4139
|
-
|
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
|
-
|
4143
|
-
|
4144
|
-
|
4145
|
-
|
4146
|
-
|
4147
|
-
|
4148
|
-
|
4149
|
-
|
4150
|
-
|
4151
|
-
|
4152
|
-
|
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
|
5117
|
+
### Color The Shapes
|
4849
5118
|
|
4850
|
-
[examples/
|
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/
|
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/
|
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-
|
4867
|
-
![glimmer-dsl-libui-mac-color-the-
|
4868
|
-
![glimmer-dsl-libui-mac-color-the-
|
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-
|
4873
|
-
![glimmer-dsl-libui-linux-color-the-
|
4874
|
-
![glimmer-dsl-libui-linux-color-the-
|
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
|
5150
|
+
class ColorTheShapes
|
4882
5151
|
include Glimmer
|
4883
5152
|
|
4884
5153
|
WINDOW_WIDTH = 800
|
4885
5154
|
WINDOW_HEIGHT = 600
|
4886
|
-
|
4887
|
-
|
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
|
-
@
|
5168
|
+
@shapes_data = []
|
4899
5169
|
@score = 0
|
4900
5170
|
@time_max = TIME_MAX_HARD
|
5171
|
+
@game_over = false
|
4901
5172
|
register_observers
|
4902
|
-
|
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
|
5192
|
+
def setup_shape_factory
|
4920
5193
|
consumer = Proc.new do
|
4921
|
-
|
4922
|
-
|
4923
|
-
|
4924
|
-
|
4925
|
-
|
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
|
4934
|
-
|
4935
|
-
|
4936
|
-
|
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
|
-
@
|
4939
|
-
args: [
|
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
|
-
@
|
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
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
5048
|
-
|
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
|
-
|
5051
|
-
|
5052
|
-
fill :
|
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
|
-
|
5066
|
-
|
5067
|
-
|
5068
|
-
|
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
|
-
|
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
|