glimmer-dsl-libui 0.2.21 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.21
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.3.0
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
  [![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)
@@ -143,7 +143,7 @@ window('Area Gallery', 400, 400) {
143
143
  }
144
144
  text(161, 40, 100) { # x, y, width
145
145
  string('Area Gallery') {
146
- font family: 'Arial', size: 14
146
+ font family: 'Arial', size: (OS.mac? ? 14 : 11)
147
147
  color :black
148
148
  }
149
149
  }
@@ -218,9 +218,10 @@ NOTE: [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) is fe
218
218
  Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interested in:
219
219
  - [glimmer-dsl-swt](https://github.com/AndyObtiva/glimmer-dsl-swt): Glimmer DSL for SWT (JRuby Desktop Development GUI Framework)
220
220
  - [glimmer-dsl-opal](https://github.com/AndyObtiva/glimmer-dsl-opal): Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps)
221
+ - [glimmer-dsl-tk](https://github.com/AndyObtiva/glimmer-dsl-tk): Glimmer DSL for Tk (MRI Ruby Desktop Development GUI Library)
222
+ - [glimmer-dsl-gtk](https://github.com/AndyObtiva/glimmer-dsl-gtk): Glimmer DSL for GTK (Ruby-GNOME Desktop Development GUI Library)
221
223
  - [glimmer-dsl-xml](https://github.com/AndyObtiva/glimmer-dsl-xml): Glimmer DSL for XML (& HTML)
222
224
  - [glimmer-dsl-css](https://github.com/AndyObtiva/glimmer-dsl-css): Glimmer DSL for CSS
223
- - [glimmer-dsl-tk](https://github.com/AndyObtiva/glimmer-dsl-tk): Glimmer DSL for Tk (MRI Ruby Desktop Development GUI Library)
224
225
 
225
226
  ## Table of Contents
226
227
 
@@ -237,6 +238,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
237
238
  - [Extra Operations](#extra-operations)
238
239
  - [Table API](#table-api)
239
240
  - [Area API](#area-api)
241
+ - [Image Glimmer Custom Control](#image-glimmer-custom-control)
240
242
  - [Smart Defaults and Conventions](#smart-defaults-and-conventions)
241
243
  - [Custom Keywords](#custom-keywords)
242
244
  - [API Gotchas](#api-gotchas)
@@ -269,6 +271,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
269
271
  - [Basic Area](#basic-area)
270
272
  - [Dynamic Area](#dynamic-area)
271
273
  - [Area Gallery](#area-gallery)
274
+ - [Basic Image](#basic-image)
272
275
  - [Histogram](#histogram)
273
276
  - [Basic Transform](#basic-transform)
274
277
  - [Login](#login)
@@ -279,6 +282,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
279
282
  - [Method-Based Custom Keyword](#method-based-custom-keyword)
280
283
  - [Tetris](#tetris)
281
284
  - [Tic Tac Toe](#tic-tac-toe)
285
+ - [Snake](#snake)
282
286
  - [Applications](#applications)
283
287
  - [Manga2PDF](#manga2pdf)
284
288
  - [Befunge98 GUI](#befunge98-gui)
@@ -371,7 +375,7 @@ gem install glimmer-dsl-libui
371
375
  Or install via Bundler `Gemfile`:
372
376
 
373
377
  ```ruby
374
- gem 'glimmer-dsl-libui', '~> 0.2.21'
378
+ gem 'glimmer-dsl-libui', '~> 0.3.0'
375
379
  ```
376
380
 
377
381
  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.
@@ -471,7 +475,7 @@ Keyword(Args) | Properties | Listeners
471
475
  `group(text as String)` | `margined` (Boolean), `title` (`String`) | None
472
476
  `horizontal_box` | `padded` (Boolean) | None
473
477
  `horizontal_separator` | None | None
474
- `image(width as Numeric, height as Numeric)` | None | None
478
+ `image(file as String = nil, width as Numeric = nil, height as Numeric = nil)` | None | None
475
479
  `image_part(pixels as String [encoded image rgba byte array], width as Numeric, height as Numeric, byte_stride as Numeric [usually width*4])` | None | None
476
480
  `image_column(name as String)` | None | None
477
481
  `image_text_column(name as String)` | None | None
@@ -550,8 +554,8 @@ There are additional useful `Glimmer::LibUI` operations that are not found in `L
550
554
  - `Glimmer::LibUI::integer_to_boolean(int, allow_nil: true)`
551
555
  - `Glimmer::LibUI::boolean_to_integer(int, allow_nil: true)`
552
556
  - `Glimmer::LibUI::degrees_to_radians(degrees)`
553
- - `Glimmer::LibUI::interpret_color(value)`: interprets a color in any form like `String`, `Symbol`, or hex into an rgb `Hash`
554
- - `Glimmer::LibUI::hex_to_rgb(value)`: converts a hex color to an rgb `Hash`
557
+ - `Glimmer::LibUI::interpret_color(value)`: interprets a color in any form like `String`, `Symbol`, or hex into an rgb `Hash` (including `0x1f3b5d`, `'0x1f3b5d'`, `'#1f3b5d'`, and 3-char hex-shorthand variations)
558
+ - `Glimmer::LibUI::hex_to_rgb(value)`: converts a hex color to an rgb `Hash` (including `0x1f3b5d`, `'0x1f3b5d'`, `'#1f3b5d'`, and 3-char hex-shorthand variations)
555
559
  - `Glimmer::LibUI::enum_names`: provides all possible enum names to use with `Glimmer::LibUI::enum_symbols(enum_name)`
556
560
  - `Glimmer::LibUI::enum_symbols(enum_name)`: returns all possible values for an enum. `enum_name` can be:
557
561
  - `:draw_brush_type`: `[:solid, :linear_gradient, :radial_gradient, :image]`
@@ -899,9 +903,11 @@ transform m1
899
903
  # and then reuse m1 elsewhere too
900
904
  ```
901
905
 
906
+ You can set a `matrix`/`transform` on `area` directly to conveniently apply to all nested `path`s too.
907
+
902
908
  Note that `area`, `path`, and nested shapes are all truly declarative, meaning they do not care about the ordering of calls to `fill`, `stroke`, and `transform`. Furthermore, any transform that is applied is reversed at the end of the block, so you never have to worry about the ordering of `transform` calls among different paths. You simply set a transform on the `path`s that need it and it is guaranteed to be called before all its content is drawn, and then undone afterwards to avoid affecting later paths. Matrix `transform` can be set on an entire `area` too, applying to all nested `path`s.
903
909
 
904
- `fill` and `stroke` accept [X11](https://en.wikipedia.org/wiki/X11_color_names) color `Symbol`s/`String`s like `:skyblue` and `'sandybrown'` or 6-number hex or 3-number hex-shorthand (as `Integer` or `String` with or without `0x` prefix)
910
+ `fill` and `stroke` accept [X11](https://en.wikipedia.org/wiki/X11_color_names) color `Symbol`s/`String`s like `:skyblue` and `'sandybrown'` or 6-char hex or 3-char hex-shorthand (as `Integer` or `String` with or without `0x` prefix)
905
911
 
906
912
  Available [X11 colors](https://en.wikipedia.org/wiki/X11_color_names) can be obtained through `Glimmer::LibUI.x11_colors` method.
907
913
 
@@ -958,6 +964,172 @@ window('area text drawing') {
958
964
  }.show
959
965
  ```
960
966
 
967
+ #### Image Glimmer Custom Control
968
+
969
+ **(ALPHA FEATURE)**
970
+
971
+ [libui](https://github.com/andlabs/libui) does not support `image` rendering outside of `table` yet.
972
+ However, [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) adds a special `image` custom control that renders an image unto an `area` pixel by pixel (and when possible to optimize, line by line).
973
+
974
+ Given that it is not a [libui](https://github.com/andlabs/libui)-native control, please keep these notes in mind:
975
+ - [libui](https://github.com/andlabs/libui) pixel-by-pixel rendering performance is slow
976
+ - Including an `image` inside an `area` `on_draw` listener improves performance due to not retaining pixel/line data in memory.
977
+ - Supplying `width` and `height` (2nd and 3rd arguments) greatly improves performance when shrinking image
978
+
979
+ Currently, it is recommended to use `image` with very small `width` and `height` values only.
980
+
981
+ Setting a `transform` `matrix` is supported under `image` just like it is under `path` and `text` inside `area`.
982
+
983
+ Example of using `image` declaratively (you may copy/paste in [`girb`](#girb-glimmer-irb)):
984
+
985
+ ![Basic Image](/images/glimmer-dsl-libui-mac-basic-image.png)
986
+
987
+ ```ruby
988
+ require 'glimmer-dsl-libui'
989
+
990
+ include Glimmer
991
+
992
+ window('Basic Image', 96, 96) {
993
+ area {
994
+ image(File.expand_path('icons/glimmer.png', __dir__), 96, 96)
995
+ }
996
+ }.show
997
+ ```
998
+
999
+ Example of better performance via `on_draw` (you may copy/paste in [`girb`](#girb-glimmer-irb)):
1000
+
1001
+ ```ruby
1002
+ require 'glimmer-dsl-libui'
1003
+
1004
+ include Glimmer
1005
+
1006
+ window('Basic Image', 96, 96) {
1007
+ area {
1008
+ on_draw do |area_draw_params|
1009
+ image(File.expand_path('icons/glimmer.png', __dir__), 96, 96)
1010
+ end
1011
+ }
1012
+ }.show
1013
+ ```
1014
+
1015
+ Example of using `image` declaratively with explicit properties (you may copy/paste in [`girb`](#girb-glimmer-irb)):
1016
+
1017
+ ```ruby
1018
+ require 'glimmer-dsl-libui'
1019
+
1020
+ include Glimmer
1021
+
1022
+ window('Basic Image', 96, 96) {
1023
+ area {
1024
+ image {
1025
+ file File.expand_path('icons/glimmer.png', __dir__)
1026
+ width 96
1027
+ height 96
1028
+ }
1029
+ }
1030
+ }.show
1031
+ ```
1032
+
1033
+ Example of better performance via `on_draw` with explicit properties (you may copy/paste in [`girb`](#girb-glimmer-irb)):
1034
+
1035
+ ```ruby
1036
+ require 'glimmer-dsl-libui'
1037
+
1038
+ include Glimmer
1039
+
1040
+ window('Basic Image', 96, 96) {
1041
+ area {
1042
+ on_draw do |area_draw_params|
1043
+ image {
1044
+ file File.expand_path('icons/glimmer.png', __dir__)
1045
+ width 96
1046
+ height 96
1047
+ }
1048
+ end
1049
+ }
1050
+ }.show
1051
+ ```
1052
+
1053
+ If you need to render an image pixel by pixel (e.g. to support a format other than `.png`) for very exceptional scenarios, you may use this example as a guide, including a line-merge optimization for neighboring horizontal pixels with the same color:
1054
+
1055
+ ```ruby
1056
+ # This is the manual way of rendering an image unto an area control.
1057
+ # It could come in handy in special situations.
1058
+ # Otherwise, it is recommended to simply utilize the `image` control that
1059
+ # can be nested under area or area on_draw listener to automate all this work.
1060
+
1061
+ require 'glimmer-dsl-libui'
1062
+ require 'chunky_png'
1063
+
1064
+ include Glimmer
1065
+
1066
+ puts 'Parsing image...'; $stdout.flush
1067
+
1068
+ f = File.open(File.expand_path('icons/glimmer.png', __dir__))
1069
+ canvas = ChunkyPNG::Canvas.from_io(f)
1070
+ f.close
1071
+ canvas.resample_nearest_neighbor!(96, 96)
1072
+ data = canvas.to_rgba_stream
1073
+ width = canvas.width
1074
+ height = canvas.height
1075
+ puts "Image width: #{width}"
1076
+ puts "Image height: #{height}"
1077
+
1078
+ puts 'Parsing colors...'; $stdout.flush
1079
+
1080
+ color_maps = height.times.map do |y|
1081
+ width.times.map do |x|
1082
+ r = data[(y*width + x)*4].ord
1083
+ g = data[(y*width + x)*4 + 1].ord
1084
+ b = data[(y*width + x)*4 + 2].ord
1085
+ a = data[(y*width + x)*4 + 3].ord
1086
+ {x: x, y: y, color: {r: r, g: g, b: b, a: a}}
1087
+ end
1088
+ end.flatten
1089
+ puts "#{color_maps.size} pixels to render..."; $stdout.flush
1090
+
1091
+ puts 'Parsing shapes...'; $stdout.flush
1092
+
1093
+ shape_maps = []
1094
+ original_color_maps = color_maps.dup
1095
+ indexed_original_color_maps = Hash[original_color_maps.each_with_index.to_a]
1096
+ color_maps.each do |color_map|
1097
+ index = indexed_original_color_maps[color_map]
1098
+ @rectangle_start_x ||= color_map[:x]
1099
+ @rectangle_width ||= 1
1100
+ if color_map[:x] < width - 1 && color_map[:color] == original_color_maps[index + 1][:color]
1101
+ @rectangle_width += 1
1102
+ else
1103
+ if color_map[:x] > 0 && color_map[:color] == original_color_maps[index - 1][:color]
1104
+ shape_maps << {x: @rectangle_start_x, y: color_map[:y], width: @rectangle_width, height: 1, color: color_map[:color]}
1105
+ else
1106
+ shape_maps << {x: color_map[:x], y: color_map[:y], width: 1, height: 1, color: color_map[:color]}
1107
+ end
1108
+ @rectangle_width = 1
1109
+ @rectangle_start_x = color_map[:x] == width - 1 ? 0 : color_map[:x] + 1
1110
+ end
1111
+ end
1112
+ puts "#{shape_maps.size} shapes to render..."; $stdout.flush
1113
+
1114
+ puts 'Rendering image...'; $stdout.flush
1115
+
1116
+ window('Basic Image', 96, 96) {
1117
+ area {
1118
+ on_draw do |area_draw_params|
1119
+ shape_maps.each do |shape_map|
1120
+ path {
1121
+ rectangle(shape_map[:x], shape_map[:y], shape_map[:width], shape_map[:height])
1122
+
1123
+ fill shape_map[:color]
1124
+ }
1125
+ end
1126
+ end
1127
+ }
1128
+ }.show
1129
+ ```
1130
+
1131
+ Check out [examples/basic_image.rb](#basic-image) (all versions) for examples of using `image` Glimmer custom control.
1132
+
961
1133
  ### Smart Defaults and Conventions
962
1134
 
963
1135
  - `horizontal_box`, `vertical_box`, `grid`, and `form` controls have `padded` as `true` upon instantiation to ensure more user-friendly GUI by default
@@ -980,7 +1152,7 @@ window('area text drawing') {
980
1152
  - When destroying a control nested under a `form`, it is automatically deleted from the form's children
981
1153
  - When destroying a control nested under a `window` or `group`, it is automatically unset as their child to allow successful destruction
982
1154
  - For `date_time_picker`, `date_picker`, and `time_picker`, make sure `time` hash values for `mon`, `wday`, and `yday` are 1-based instead of [libui](https://github.com/andlabs/libui) original 0-based values, and return `dst` as Boolean instead of `isdst` as `1`/`0`
983
- - Smart defaults for `grid` child attributes are `left` (`0`), `top` (`0`), `xspan` (`1`), `yspan` (`1`), `hexpand` (`false`), `halign` (`:fill`), `vexpand` (`false`), and `valign` (`:fill`)
1155
+ - Smart defaults for `grid` child properties are `left` (`0`), `top` (`0`), `xspan` (`1`), `yspan` (`1`), `hexpand` (`false`), `halign` (`:fill`), `vexpand` (`false`), and `valign` (`:fill`)
984
1156
  - The `table` control automatically constructs required `TableModelHandler`, `TableModel`, and `TableParams`, calculating all their arguments from `cell_rows` and `editable` properties (e.g. `NumRows`) as well as nested columns (e.g. `text_column`)
985
1157
  - Table model instances are automatically freed from memory after `window` is destroyed.
986
1158
  - Table `cell_rows` data has implicit data-binding to table cell values for deletion, insertion, and change (done by diffing `cell_rows` value before and after change and auto-informing `table` of deletions [`LibUI.table_model_row_deleted`], insertions [`LibUI.table_model_row_deleted`], and changes [`LibUI.table_model_row_changed`]). When deleting data rows from `cell_rows` array, then actual rows from the `table` are automatically deleted. When inserting data rows into `cell_rows` array, then actual `table` rows are automatically inserted. When updating data rows in `cell_rows` array, then actual `table` rows are automatically updated.
@@ -994,7 +1166,7 @@ window('area text drawing') {
994
1166
  - All controls are protected from garbage collection until no longer needed (explicitly destroyed), so there is no need to worry about surprises.
995
1167
  - All resources are freed automatically once no longer needed or left to garbage collection.
996
1168
  - 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.
997
- - 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)
1169
+ - 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-char hex or 3-char hex (as `Integer` or `String` with or without `0x` prefix)
998
1170
  - Color alpha value defaults to `1.0` when not specified.
999
1171
 
1000
1172
  ### Custom Keywords
@@ -1221,7 +1393,7 @@ class MetaExample
1221
1393
 
1222
1394
  def run_example(example)
1223
1395
  Thread.new do
1224
- command = "ruby -r #{glimmer_dsl_libui_file} #{example} 2>&1"
1396
+ command = "#{RbConfig.ruby} -r #{glimmer_dsl_libui_file} #{example} 2>&1"
1225
1397
  result = ''
1226
1398
  IO.popen(command) do |f|
1227
1399
  sleep(0.0001) # yield to main thread
@@ -1758,11 +1930,11 @@ class TinyMidiPlayer
1758
1930
 
1759
1931
  UI.new_horizontal_box.tap do |hbox|
1760
1932
  UI.new_vertical_box.tap do |vbox|
1761
- UI.new_button('').tap do |button1|
1933
+ UI.new_button('â–¶').tap do |button1|
1762
1934
  UI.button_on_clicked(button1) { play_midi }
1763
1935
  UI.box_append(vbox, button1, 1)
1764
1936
  end
1765
- UI.new_button('').tap do |button2|
1937
+ UI.new_button('â– ').tap do |button2|
1766
1938
  UI.button_on_clicked(button2) { stop_midi }
1767
1939
  UI.box_append(vbox, button2, 1)
1768
1940
  end
@@ -1856,12 +2028,12 @@ class TinyMidiPlayer
1856
2028
  vertical_box {
1857
2029
  stretchy false
1858
2030
 
1859
- button('') {
2031
+ button('â–¶') {
1860
2032
  on_clicked do
1861
2033
  play_midi
1862
2034
  end
1863
2035
  }
1864
- button('') {
2036
+ button('â– ') {
1865
2037
  on_clicked do
1866
2038
  stop_midi
1867
2039
  end
@@ -2992,13 +3164,7 @@ window('Editable column animal sounds', 400, 200) {
2992
3164
 
2993
3165
  ### Basic Table Image
2994
3166
 
2995
- This example requires pre-installing `chunky_png` Ruby gem:
2996
-
2997
- ```
2998
- gem install chunky_png -v1.4.0
2999
- ```
3000
-
3001
- Also, note that behavior varies per platform (i.e. how `table` chooses to size images by default).
3167
+ Note that behavior varies per platform (i.e. how `table` chooses to size images by default).
3002
3168
 
3003
3169
  [examples/basic_table_image.rb](examples/basic_table_image.rb)
3004
3170
 
@@ -3154,13 +3320,7 @@ window('The Red Turtle', 310, 350, false) {
3154
3320
 
3155
3321
  ### Basic Table Image Text
3156
3322
 
3157
- This example has a prerequisite of installing `chunky_png` Ruby gem:
3158
-
3159
- ```
3160
- gem install chunky_png -v1.4.0
3161
- ```
3162
-
3163
- Also, note that behavior varies per platform (i.e. how `table` chooses to size images by default).
3323
+ Note that behavior varies per platform (i.e. how `table` chooses to size images by default).
3164
3324
 
3165
3325
  [examples/basic_table_image_text.rb](examples/basic_table_image_text.rb)
3166
3326
 
@@ -3480,12 +3640,6 @@ window('Task Progress', 300, 200) {
3480
3640
 
3481
3641
  ### Basic Table Color
3482
3642
 
3483
- This example requires pre-installing `chunky_png` Ruby gem:
3484
-
3485
- ```
3486
- gem install chunky_png -v1.4.0
3487
- ```
3488
-
3489
3643
  [examples/basic_table_color.rb](examples/basic_table_color.rb)
3490
3644
 
3491
3645
  Run with this command from the root of the project if you cloned the project:
@@ -4127,7 +4281,7 @@ window('Area Gallery', 400, 400) {
4127
4281
  }
4128
4282
  text(161, 40, 100) { # x, y, width
4129
4283
  string('Area Gallery') {
4130
- font family: 'Arial', size: 14
4284
+ font family: 'Arial', size: (OS.mac? ? 14 : 11)
4131
4285
  color :black
4132
4286
  }
4133
4287
  }
@@ -4337,7 +4491,7 @@ window('Area Gallery', 400, 400) {
4337
4491
  width 100
4338
4492
 
4339
4493
  string {
4340
- font family: 'Arial', size: 14
4494
+ font family: 'Arial', size: (OS.mac? ? 14 : 11)
4341
4495
  color :black
4342
4496
 
4343
4497
  'Area Gallery'
@@ -4450,7 +4604,7 @@ window('Area Gallery', 400, 400) {
4450
4604
  }
4451
4605
  text(161, 40, 100) { # x, y, width
4452
4606
  string('Area Gallery') {
4453
- font family: 'Arial', size: 14
4607
+ font family: 'Arial', size: (OS.mac? ? 14 : 11)
4454
4608
  color :black
4455
4609
  }
4456
4610
  }
@@ -4662,7 +4816,7 @@ window('Area Gallery', 400, 400) {
4662
4816
  width 100
4663
4817
 
4664
4818
  string {
4665
- font family: 'Arial', size: 14
4819
+ font family: 'Arial', size: (OS.mac? ? 14 : 11)
4666
4820
  color :black
4667
4821
 
4668
4822
  'Area Gallery'
@@ -4721,6 +4875,196 @@ window('Area Gallery', 400, 400) {
4721
4875
  }.show
4722
4876
  ```
4723
4877
 
4878
+ ### Basic Image
4879
+
4880
+ [examples/basic_image.rb](examples/basic_image.rb)
4881
+
4882
+ Run with this command from the root of the project if you cloned the project:
4883
+
4884
+ ```
4885
+ ruby -r './lib/glimmer-dsl-libui' examples/basic_image.rb
4886
+ ```
4887
+
4888
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
4889
+
4890
+ ```
4891
+ ruby -r glimmer-dsl-libui -e "require 'examples/basic_image'"
4892
+ ```
4893
+
4894
+ Mac
4895
+
4896
+ ![glimmer-dsl-libui-mac-basic-image.png](images/glimmer-dsl-libui-mac-basic-image.png)
4897
+
4898
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
4899
+
4900
+ ```ruby
4901
+ require 'glimmer-dsl-libui'
4902
+
4903
+ include Glimmer
4904
+
4905
+ window('Basic Image', 96, 96) {
4906
+ area {
4907
+ # image is not a real LibUI control. It is built in Glimmer as a custom control that renders
4908
+ # tiny pixels/lines as rectangle paths. As such, it does not have good performance, but can
4909
+ # be used in exceptional circumstances where an image control is really needed.
4910
+ #
4911
+ # Furthermore, adding image directly under area is even slower due to taking up more memory for every
4912
+ # image pixel rendered. Check basic_image2.rb for a faster alternative using on_draw manually.
4913
+ #
4914
+ # It is recommended to pass width/height args to shrink image and achieve faster performance.
4915
+ image(File.expand_path('../icons/glimmer.png', __dir__), 96, 96)
4916
+ }
4917
+ }.show
4918
+ ```
4919
+
4920
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (better performance via `on_draw`):
4921
+
4922
+ ```ruby
4923
+ # frozen_string_literal: true
4924
+
4925
+ require 'glimmer-dsl-libui'
4926
+
4927
+ include Glimmer
4928
+
4929
+ window('Basic Image', 96, 96) {
4930
+ area {
4931
+ on_draw do |area_draw_params|
4932
+ image(File.expand_path('../icons/glimmer.png', __dir__), 96, 96)
4933
+ end
4934
+ }
4935
+ }.show
4936
+ ```
4937
+
4938
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (explicit properties):
4939
+
4940
+ ```ruby
4941
+ # frozen_string_literal: true
4942
+
4943
+ require 'glimmer-dsl-libui'
4944
+
4945
+ include Glimmer
4946
+
4947
+ window('Basic Image', 96, 96) {
4948
+ area {
4949
+ # image is not a real LibUI control. It is built in Glimmer as a custom control that renders
4950
+ # tiny pixels/lines as rectangle paths. As such, it does not have good performance, but can
4951
+ # be used in exceptional circumstances where an image control is really needed.
4952
+ #
4953
+ # Furthermore, adding image directly under area is even slower due to taking up more memory for every
4954
+ # image pixel rendered. Check basic_image4.rb for a faster alternative using on_draw manually.
4955
+ #
4956
+ # It is recommended to pass width/height args to shrink image and achieve faster performance.
4957
+ image {
4958
+ file File.expand_path('../icons/glimmer.png', __dir__)
4959
+ width 96
4960
+ height 96
4961
+ }
4962
+ }
4963
+ }.show
4964
+ ```
4965
+
4966
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (better performance with `on_draw` when setting explicit properties):
4967
+
4968
+ ```ruby
4969
+ # frozen_string_literal: true
4970
+
4971
+ require 'glimmer-dsl-libui'
4972
+
4973
+ include Glimmer
4974
+
4975
+ window('Basic Image', 96, 96) {
4976
+ area {
4977
+ on_draw do |area_draw_params|
4978
+ image {
4979
+ file File.expand_path('../icons/glimmer.png', __dir__)
4980
+ width 96
4981
+ height 96
4982
+ }
4983
+ end
4984
+ }
4985
+ }.show
4986
+ ```
4987
+
4988
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 5 (fully manual pixel-by-pixel rendering):
4989
+
4990
+ ```ruby
4991
+ # frozen_string_literal: true
4992
+
4993
+ # This is the manual way of rendering an image unto an area control.
4994
+ # It could come in handy in special situations.
4995
+ # Otherwise, it is recommended to simply utilize the `image` control that
4996
+ # can be nested under area or area on_draw listener to automate all this work.
4997
+
4998
+ require 'glimmer-dsl-libui'
4999
+ require 'chunky_png'
5000
+
5001
+ include Glimmer
5002
+
5003
+ puts 'Parsing image...'; $stdout.flush
5004
+
5005
+ f = File.open(File.expand_path('../icons/glimmer.png', __dir__))
5006
+ canvas = ChunkyPNG::Canvas.from_io(f)
5007
+ f.close
5008
+ canvas.resample_nearest_neighbor!(96, 96)
5009
+ data = canvas.to_rgba_stream
5010
+ width = canvas.width
5011
+ height = canvas.height
5012
+ puts "Image width: #{width}"
5013
+ puts "Image height: #{height}"
5014
+
5015
+ puts 'Parsing colors...'; $stdout.flush
5016
+
5017
+ color_maps = height.times.map do |y|
5018
+ width.times.map do |x|
5019
+ r = data[(y*width + x)*4].ord
5020
+ g = data[(y*width + x)*4 + 1].ord
5021
+ b = data[(y*width + x)*4 + 2].ord
5022
+ a = data[(y*width + x)*4 + 3].ord
5023
+ {x: x, y: y, color: {r: r, g: g, b: b, a: a}}
5024
+ end
5025
+ end.flatten
5026
+ puts "#{color_maps.size} pixels to render..."; $stdout.flush
5027
+
5028
+ puts 'Parsing shapes...'; $stdout.flush
5029
+
5030
+ shape_maps = []
5031
+ original_color_maps = color_maps.dup
5032
+ indexed_original_color_maps = Hash[original_color_maps.each_with_index.to_a]
5033
+ color_maps.each do |color_map|
5034
+ index = indexed_original_color_maps[color_map]
5035
+ @rectangle_start_x ||= color_map[:x]
5036
+ @rectangle_width ||= 1
5037
+ if color_map[:x] < width - 1 && color_map[:color] == original_color_maps[index + 1][:color]
5038
+ @rectangle_width += 1
5039
+ else
5040
+ if color_map[:x] > 0 && color_map[:color] == original_color_maps[index - 1][:color]
5041
+ shape_maps << {x: @rectangle_start_x, y: color_map[:y], width: @rectangle_width, height: 1, color: color_map[:color]}
5042
+ else
5043
+ shape_maps << {x: color_map[:x], y: color_map[:y], width: 1, height: 1, color: color_map[:color]}
5044
+ end
5045
+ @rectangle_width = 1
5046
+ @rectangle_start_x = color_map[:x] == width - 1 ? 0 : color_map[:x] + 1
5047
+ end
5048
+ end
5049
+ puts "#{shape_maps.size} shapes to render..."; $stdout.flush
5050
+
5051
+ puts 'Rendering image...'; $stdout.flush
5052
+
5053
+ window('Basic Image', 96, 96) {
5054
+ area {
5055
+ on_draw do |area_draw_params|
5056
+ shape_maps.each do |shape_map|
5057
+ path {
5058
+ rectangle(shape_map[:x], shape_map[:y], shape_map[:width], shape_map[:height])
5059
+
5060
+ fill shape_map[:color]
5061
+ }
5062
+ end
5063
+ end
5064
+ }
5065
+ }.show
5066
+ ```
5067
+
4724
5068
  ### Histogram
4725
5069
 
4726
5070
  [examples/histogram.rb](examples/histogram.rb)
@@ -6290,6 +6634,22 @@ Mac
6290
6634
 
6291
6635
  ![glimmer-dsl-libui-mac-tetris-high-scores.png](images/glimmer-dsl-libui-mac-tetris-high-scores.png)
6292
6636
 
6637
+ Windows
6638
+
6639
+ ![glimmer-dsl-libui-windows-tetris.png](images/glimmer-dsl-libui-windows-tetris.png)
6640
+
6641
+ ![glimmer-dsl-libui-windows-tetris-game-over.png](images/glimmer-dsl-libui-windows-tetris-game-over.png)
6642
+
6643
+ ![glimmer-dsl-libui-windows-tetris-high-scores.png](images/glimmer-dsl-libui-windows-tetris-high-scores.png)
6644
+
6645
+ Linux
6646
+
6647
+ ![glimmer-dsl-libui-linux-tetris.png](images/glimmer-dsl-libui-linux-tetris.png)
6648
+
6649
+ ![glimmer-dsl-libui-linux-tetris-game-over.png](images/glimmer-dsl-libui-linux-tetris-game-over.png)
6650
+
6651
+ ![glimmer-dsl-libui-linux-tetris-high-scores.png](images/glimmer-dsl-libui-linux-tetris-high-scores.png)
6652
+
6293
6653
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
6294
6654
 
6295
6655
  ```ruby
@@ -6530,7 +6890,19 @@ class Tetris
6530
6890
  on_key_down do |key_event|
6531
6891
  case key_event
6532
6892
  in ext_key: :down
6533
- @game.down!
6893
+ if OS.windows?
6894
+ # rate limit downs in Windows as they go too fast when key is held
6895
+ @queued_downs ||= 0
6896
+ if @queued_downs < 2
6897
+ @queued_downs += 1
6898
+ Glimmer::LibUI.timer(0.01, repeat: false) do
6899
+ @game.down! if @queued_downs < 2
6900
+ @queued_downs -= 1
6901
+ end
6902
+ end
6903
+ else
6904
+ @game.down!
6905
+ end
6534
6906
  in key: ' '
6535
6907
  @game.down!(instant: true)
6536
6908
  in ext_key: :up
@@ -6614,8 +6986,11 @@ class Tetris
6614
6986
  end
6615
6987
 
6616
6988
  def start_moving_tetrominos_down
6617
- Glimmer::LibUI.timer(@game.delay) do
6618
- @game.down! if !@game.game_over? && !@game.paused?
6989
+ unless @tetrominos_start_moving_down
6990
+ @tetrominos_start_moving_down = true
6991
+ Glimmer::LibUI.timer(@game.delay) do
6992
+ @game.down! if !@game.game_over? && !@game.paused?
6993
+ end
6619
6994
  end
6620
6995
  end
6621
6996
 
@@ -6628,6 +7003,8 @@ class Tetris
6628
7003
 
6629
7004
  def show_high_scores
6630
7005
  Glimmer::LibUI.queue_main do
7006
+ game_paused = !!@game.paused
7007
+ @game.paused = true
6631
7008
  if @game.high_scores.empty?
6632
7009
  high_scores_string = "No games have been scored yet."
6633
7010
  else
@@ -6636,6 +7013,7 @@ class Tetris
6636
7013
  end.join("\n")
6637
7014
  end
6638
7015
  msg_box('High Scores', high_scores_string)
7016
+ @game.paused = game_paused
6639
7017
  end
6640
7018
  end
6641
7019
 
@@ -6675,6 +7053,26 @@ Mac
6675
7053
 
6676
7054
  ![glimmer-dsl-libui-mac-tic-tac-toe-draw.png](images/glimmer-dsl-libui-mac-tic-tac-toe-draw.png)
6677
7055
 
7056
+ Windows
7057
+
7058
+ ![glimmer-dsl-libui-windows-tic-tac-toe.png](images/glimmer-dsl-libui-windows-tic-tac-toe.png)
7059
+
7060
+ ![glimmer-dsl-libui-windows-tic-tac-toe-player-o-wins.png](images/glimmer-dsl-libui-windows-tic-tac-toe-player-o-wins.png)
7061
+
7062
+ ![glimmer-dsl-libui-windows-tic-tac-toe-player-x-wins.png](images/glimmer-dsl-libui-windows-tic-tac-toe-player-x-wins.png)
7063
+
7064
+ ![glimmer-dsl-libui-windows-tic-tac-toe-draw.png](images/glimmer-dsl-libui-windows-tic-tac-toe-draw.png)
7065
+
7066
+ Linux
7067
+
7068
+ ![glimmer-dsl-libui-linux-tic-tac-toe.png](images/glimmer-dsl-libui-linux-tic-tac-toe.png)
7069
+
7070
+ ![glimmer-dsl-libui-linux-tic-tac-toe-player-o-wins.png](images/glimmer-dsl-libui-linux-tic-tac-toe-player-o-wins.png)
7071
+
7072
+ ![glimmer-dsl-libui-linux-tic-tac-toe-player-x-wins.png](images/glimmer-dsl-libui-linux-tic-tac-toe-player-x-wins.png)
7073
+
7074
+ ![glimmer-dsl-libui-linux-tic-tac-toe-draw.png](images/glimmer-dsl-libui-linux-tic-tac-toe-draw.png)
7075
+
6678
7076
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
6679
7077
 
6680
7078
  ```ruby
@@ -6765,6 +7163,137 @@ end
6765
7163
  TicTacToe.new.launch
6766
7164
  ```
6767
7165
 
7166
+ ### Snake
7167
+
7168
+ Snake provides an example of building a desktop application [test-first](/spec/examples/snake/model/game_spec.rb) following the MVP ([Model](/examples/snake/model/game.rb) / [View](/examples/snake.rb) / [Presenter](/examples/snake/presenter/grid.rb)) architectural pattern.
7169
+
7170
+ [examples/snake.rb](examples/snake.rb)
7171
+
7172
+ Run with this command from the root of the project if you cloned the project:
7173
+
7174
+ ```
7175
+ ruby -r './lib/glimmer-dsl-libui' examples/snake.rb
7176
+ ```
7177
+
7178
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
7179
+
7180
+ ```
7181
+ ruby -r glimmer-dsl-libui -e "require 'examples/snake'"
7182
+ ```
7183
+
7184
+ Mac
7185
+
7186
+ ![glimmer-dsl-libui-mac-snake.png](images/glimmer-dsl-libui-mac-snake.png)
7187
+
7188
+ ![glimmer-dsl-libui-mac-snake-game-over.png](images/glimmer-dsl-libui-mac-snake-game-over.png)
7189
+
7190
+ Windows
7191
+
7192
+ ![glimmer-dsl-libui-windows-snake.png](images/glimmer-dsl-libui-windows-snake.png)
7193
+
7194
+ ![glimmer-dsl-libui-windows-snake-game-over.png](images/glimmer-dsl-libui-windows-snake-game-over.png)
7195
+
7196
+ Linux
7197
+
7198
+ ![glimmer-dsl-libui-linux-snake.png](images/glimmer-dsl-libui-linux-snake.png)
7199
+
7200
+ ![glimmer-dsl-libui-linux-snake-game-over.png](images/glimmer-dsl-libui-linux-snake-game-over.png)
7201
+
7202
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
7203
+
7204
+ ```ruby
7205
+ require 'glimmer-dsl-libui'
7206
+ require 'glimmer/data_binding/observer'
7207
+
7208
+ require_relative 'snake/presenter/grid'
7209
+
7210
+ class Snake
7211
+ CELL_SIZE = 15
7212
+ SNAKE_MOVE_DELAY = 0.1
7213
+ include Glimmer
7214
+
7215
+ def initialize
7216
+ @game = Model::Game.new
7217
+ @grid = Presenter::Grid.new(@game)
7218
+ @game.start
7219
+ create_gui
7220
+ register_observers
7221
+ end
7222
+
7223
+ def launch
7224
+ @main_window.show
7225
+ end
7226
+
7227
+ def register_observers
7228
+ @game.height.times do |row|
7229
+ @game.width.times do |column|
7230
+ Glimmer::DataBinding::Observer.proc do |new_color|
7231
+ @cell_grid[row][column].fill = new_color
7232
+ end.observe(@grid.cells[row][column], :color)
7233
+ end
7234
+ end
7235
+
7236
+ Glimmer::DataBinding::Observer.proc do |game_over|
7237
+ Glimmer::LibUI.queue_main do
7238
+ if game_over
7239
+ msg_box('Game Over!', "Score: #{@game.score}")
7240
+ @game.start
7241
+ end
7242
+ end
7243
+ end.observe(@game, :over)
7244
+
7245
+ Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
7246
+ unless @game.over?
7247
+ @game.snake.move
7248
+ @main_window.title = "Glimmer Snake (Score: #{@game.score})"
7249
+ end
7250
+ end
7251
+ end
7252
+
7253
+ def create_gui
7254
+ @cell_grid = []
7255
+ @main_window = window('Glimmer Snake', @game.width * CELL_SIZE, @game.height * CELL_SIZE) {
7256
+ resizable false
7257
+
7258
+ vertical_box {
7259
+ padded false
7260
+
7261
+ @game.height.times do |row|
7262
+ @cell_grid << []
7263
+ horizontal_box {
7264
+ padded false
7265
+
7266
+ @game.width.times do |column|
7267
+ area {
7268
+ @cell_grid.last << path {
7269
+ square(0, 0, CELL_SIZE)
7270
+
7271
+ fill Presenter::Cell::COLOR_CLEAR
7272
+ }
7273
+
7274
+ on_key_up do |area_key_event|
7275
+ orientation_and_key = [@game.snake.head.orientation, area_key_event[:ext_key]]
7276
+ case orientation_and_key
7277
+ in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
7278
+ @game.snake.turn_right
7279
+ in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
7280
+ @game.snake.turn_left
7281
+ else
7282
+ # No Op
7283
+ end
7284
+ end
7285
+ }
7286
+ end
7287
+ }
7288
+ end
7289
+ }
7290
+ }
7291
+ end
7292
+ end
7293
+
7294
+ Snake.new.launch
7295
+ ```
7296
+
6768
7297
  ## Applications
6769
7298
 
6770
7299
  Here are some applications built with [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui)