glimmer-dsl-libui 0.2.21 → 0.3.0

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.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)