glimmer-dsl-libui 0.2.24 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9b4b92bdc276153bae236387b4813b62bc1b8b9f5b6779c59ffae9b1b47484f
4
- data.tar.gz: 69efbe44dac5e3fec15ec31c261b469187a45d8acabf2d9754459f1bbfbc66d7
3
+ metadata.gz: 82c9b768b4dd09cbbad087d470016eaa80a667151f844a57aecf4c339d8bbfe5
4
+ data.tar.gz: 4c4b1a1fec0c78d61cb7d47ce79d93766f2ec3c94e2332e185e44b35ecd9dfd8
5
5
  SHA512:
6
- metadata.gz: 39ef3e3f8db68098d52222ef7ffa3ed5f9d85e25199ed960d9945e2f92a13bd90cdfc860fcd5fd901a6b76d3899786ef9363fad131d7bc25880506b4355ef2dd
7
- data.tar.gz: 0f92e07ed0e37bfc7e172b275d57853b26407b9f708554d586a4b6b97460485926217f364cad5cfdf3b016a37366acd51dbf440004709909f5d9cf924a8d7734
6
+ metadata.gz: abd5077dd154505ac5b97986baace9c72d61a734515f39cb9213161fbd16dff85e46af725b702edfc87fae6043288f4897b53b8c1d0671dc509e0c3ce3b222b2
7
+ data.tar.gz: cb2273302f43a0b4e2543ac75c08a51414013d50600d5ccf51406799fba6122ca5144cf8a26c018a594d8e8238e899976c9f8a4a0b00505be954164095726b16
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.3.0
4
+
5
+ - Upgrade to glimmer 2.4.1
6
+ - Add `chunky_png` gem to support `.png` images natively
7
+ - New `image` Glimmer custom control that can be nested under `area` to render an image (it is not part of LibUI, so it has some performance caveats, but is better than nothing and is fast with smaller image width/height)
8
+
3
9
  ## 0.2.24
4
10
 
5
11
  - Support hex colors as `String` with `'#'` prefix (e.g. '#ffaa92')
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.24
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)
@@ -238,6 +238,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
238
238
  - [Extra Operations](#extra-operations)
239
239
  - [Table API](#table-api)
240
240
  - [Area API](#area-api)
241
+ - [Image Glimmer Custom Control](#image-glimmer-custom-control)
241
242
  - [Smart Defaults and Conventions](#smart-defaults-and-conventions)
242
243
  - [Custom Keywords](#custom-keywords)
243
244
  - [API Gotchas](#api-gotchas)
@@ -270,6 +271,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
270
271
  - [Basic Area](#basic-area)
271
272
  - [Dynamic Area](#dynamic-area)
272
273
  - [Area Gallery](#area-gallery)
274
+ - [Basic Image](#basic-image)
273
275
  - [Histogram](#histogram)
274
276
  - [Basic Transform](#basic-transform)
275
277
  - [Login](#login)
@@ -373,7 +375,7 @@ gem install glimmer-dsl-libui
373
375
  Or install via Bundler `Gemfile`:
374
376
 
375
377
  ```ruby
376
- gem 'glimmer-dsl-libui', '~> 0.2.24'
378
+ gem 'glimmer-dsl-libui', '~> 0.3.0'
377
379
  ```
378
380
 
379
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.
@@ -473,7 +475,7 @@ Keyword(Args) | Properties | Listeners
473
475
  `group(text as String)` | `margined` (Boolean), `title` (`String`) | None
474
476
  `horizontal_box` | `padded` (Boolean) | None
475
477
  `horizontal_separator` | None | None
476
- `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
477
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
478
480
  `image_column(name as String)` | None | None
479
481
  `image_text_column(name as String)` | None | None
@@ -962,6 +964,172 @@ window('area text drawing') {
962
964
  }.show
963
965
  ```
964
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
+
965
1133
  ### Smart Defaults and Conventions
966
1134
 
967
1135
  - `horizontal_box`, `vertical_box`, `grid`, and `form` controls have `padded` as `true` upon instantiation to ensure more user-friendly GUI by default
@@ -984,7 +1152,7 @@ window('area text drawing') {
984
1152
  - When destroying a control nested under a `form`, it is automatically deleted from the form's children
985
1153
  - When destroying a control nested under a `window` or `group`, it is automatically unset as their child to allow successful destruction
986
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`
987
- - 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`)
988
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`)
989
1157
  - Table model instances are automatically freed from memory after `window` is destroyed.
990
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.
@@ -2996,13 +3164,7 @@ window('Editable column animal sounds', 400, 200) {
2996
3164
 
2997
3165
  ### Basic Table Image
2998
3166
 
2999
- This example requires pre-installing `chunky_png` Ruby gem:
3000
-
3001
- ```
3002
- gem install chunky_png -v1.4.0
3003
- ```
3004
-
3005
- 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).
3006
3168
 
3007
3169
  [examples/basic_table_image.rb](examples/basic_table_image.rb)
3008
3170
 
@@ -3158,13 +3320,7 @@ window('The Red Turtle', 310, 350, false) {
3158
3320
 
3159
3321
  ### Basic Table Image Text
3160
3322
 
3161
- This example has a prerequisite of installing `chunky_png` Ruby gem:
3162
-
3163
- ```
3164
- gem install chunky_png -v1.4.0
3165
- ```
3166
-
3167
- 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).
3168
3324
 
3169
3325
  [examples/basic_table_image_text.rb](examples/basic_table_image_text.rb)
3170
3326
 
@@ -3484,12 +3640,6 @@ window('Task Progress', 300, 200) {
3484
3640
 
3485
3641
  ### Basic Table Color
3486
3642
 
3487
- This example requires pre-installing `chunky_png` Ruby gem:
3488
-
3489
- ```
3490
- gem install chunky_png -v1.4.0
3491
- ```
3492
-
3493
3643
  [examples/basic_table_color.rb](examples/basic_table_color.rb)
3494
3644
 
3495
3645
  Run with this command from the root of the project if you cloned the project:
@@ -4725,6 +4875,196 @@ window('Area Gallery', 400, 400) {
4725
4875
  }.show
4726
4876
  ```
4727
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
+
4728
5068
  ### Histogram
4729
5069
 
4730
5070
  [examples/histogram.rb](examples/histogram.rb)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.24
1
+ 0.3.0
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+
5
+ include Glimmer
6
+
7
+ window('Basic Image', 96, 96) {
8
+ area {
9
+ # image is not a real LibUI control. It is built in Glimmer as a custom control that renders
10
+ # tiny pixels/lines as rectangle paths. As such, it does not have good performance, but can
11
+ # be used in exceptional circumstances where an image control is really needed.
12
+ #
13
+ # Furthermore, adding image directly under area is even slower due to taking up more memory for every
14
+ # image pixel rendered. Check basic_image2.rb for a faster alternative using on_draw manually.
15
+ #
16
+ # It is recommended to pass width/height args to shrink image and achieve faster performance.
17
+ image(File.expand_path('../icons/glimmer.png', __dir__), 96, 96)
18
+ }
19
+ }.show
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+
5
+ include Glimmer
6
+
7
+ window('Basic Image', 96, 96) {
8
+ area {
9
+ on_draw do |area_draw_params|
10
+ image(File.expand_path('../icons/glimmer.png', __dir__), 96, 96)
11
+ end
12
+ }
13
+ }.show
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+
5
+ include Glimmer
6
+
7
+ window('Basic Image', 96, 96) {
8
+ area {
9
+ # image is not a real LibUI control. It is built in Glimmer as a custom control that renders
10
+ # tiny pixels/lines as rectangle paths. As such, it does not have good performance, but can
11
+ # be used in exceptional circumstances where an image control is really needed.
12
+ #
13
+ # Furthermore, adding image directly under area is even slower due to taking up more memory for every
14
+ # image pixel rendered. Check basic_image4.rb for a faster alternative using on_draw manually.
15
+ #
16
+ # It is recommended to pass width/height args to shrink image and achieve faster performance.
17
+ image {
18
+ file File.expand_path('../icons/glimmer.png', __dir__)
19
+ width 96
20
+ height 96
21
+ }
22
+ }
23
+ }.show
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+
5
+ include Glimmer
6
+
7
+ window('Basic Image', 96, 96) {
8
+ area {
9
+ on_draw do |area_draw_params|
10
+ image {
11
+ file File.expand_path('../icons/glimmer.png', __dir__)
12
+ width 96
13
+ height 96
14
+ }
15
+ end
16
+ }
17
+ }.show
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This is the manual way of rendering an image unto an area control.
4
+ # It could come in handy in special situations.
5
+ # Otherwise, it is recommended to simply utilize the `image` control that
6
+ # can be nested under area or area on_draw listener to automate all this work.
7
+
8
+ require 'glimmer-dsl-libui'
9
+ require 'chunky_png'
10
+
11
+ include Glimmer
12
+
13
+ puts 'Parsing image...'; $stdout.flush
14
+
15
+ f = File.open(File.expand_path('../icons/glimmer.png', __dir__))
16
+ canvas = ChunkyPNG::Canvas.from_io(f)
17
+ f.close
18
+ canvas.resample_nearest_neighbor!(96, 96)
19
+ data = canvas.to_rgba_stream
20
+ width = canvas.width
21
+ height = canvas.height
22
+ puts "Image width: #{width}"
23
+ puts "Image height: #{height}"
24
+
25
+ puts 'Parsing colors...'; $stdout.flush
26
+
27
+ color_maps = height.times.map do |y|
28
+ width.times.map do |x|
29
+ r = data[(y*width + x)*4].ord
30
+ g = data[(y*width + x)*4 + 1].ord
31
+ b = data[(y*width + x)*4 + 2].ord
32
+ a = data[(y*width + x)*4 + 3].ord
33
+ {x: x, y: y, color: {r: r, g: g, b: b, a: a}}
34
+ end
35
+ end.flatten
36
+ puts "#{color_maps.size} pixels to render..."; $stdout.flush
37
+
38
+ puts 'Parsing shapes...'; $stdout.flush
39
+
40
+ shape_maps = []
41
+ original_color_maps = color_maps.dup
42
+ indexed_original_color_maps = Hash[original_color_maps.each_with_index.to_a]
43
+ color_maps.each do |color_map|
44
+ index = indexed_original_color_maps[color_map]
45
+ @rectangle_start_x ||= color_map[:x]
46
+ @rectangle_width ||= 1
47
+ if color_map[:x] < width - 1 && color_map[:color] == original_color_maps[index + 1][:color]
48
+ @rectangle_width += 1
49
+ else
50
+ if color_map[:x] > 0 && color_map[:color] == original_color_maps[index - 1][:color]
51
+ shape_maps << {x: @rectangle_start_x, y: color_map[:y], width: @rectangle_width, height: 1, color: color_map[:color]}
52
+ else
53
+ shape_maps << {x: color_map[:x], y: color_map[:y], width: 1, height: 1, color: color_map[:color]}
54
+ end
55
+ @rectangle_width = 1
56
+ @rectangle_start_x = color_map[:x] == width - 1 ? 0 : color_map[:x] + 1
57
+ end
58
+ end
59
+ puts "#{shape_maps.size} shapes to render..."; $stdout.flush
60
+
61
+ puts 'Rendering image...'; $stdout.flush
62
+
63
+ window('Basic Image', 96, 96) {
64
+ area {
65
+ on_draw do |area_draw_params|
66
+ shape_maps.each do |shape_map|
67
+ path {
68
+ rectangle(shape_map[:x], shape_map[:y], shape_map[:width], shape_map[:height])
69
+
70
+ fill shape_map[:color]
71
+ }
72
+ end
73
+ end
74
+ }
75
+ }.show
Binary file
@@ -40,7 +40,7 @@ module Glimmer
40
40
 
41
41
  def add_content(parent, keyword, *args, &block)
42
42
  super
43
- parent.post_add_content
43
+ parent&.post_add_content
44
44
  end
45
45
  end
46
46
  end
@@ -34,7 +34,6 @@ module Glimmer
34
34
  @args = args
35
35
  @block = block
36
36
  @enabled = true
37
- @children = []
38
37
  post_add_content if @block.nil?
39
38
  end
40
39
 
@@ -20,44 +20,184 @@
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
22
  require 'glimmer/libui/control_proxy'
23
+ require 'glimmer/libui/image_path_renderer'
23
24
  require 'glimmer/data_binding/observer'
25
+ require 'glimmer/libui/control_proxy/transformable'
24
26
 
25
27
  using ArrayIncludeMethods
26
28
 
27
29
  module Glimmer
28
30
  module LibUI
29
31
  class ControlProxy
30
- # Proxy for LibUI image objects
32
+ # Proxy for LibUI image object and Glimmer custom control
31
33
  #
32
34
  # Follows the Proxy Design Pattern
33
35
  class ImageProxy < ControlProxy
36
+ include Parent
37
+ prepend Transformable
38
+
39
+ attr_reader :data, :pixels, :shapes
40
+
34
41
  def initialize(keyword, parent, args, &block)
35
42
  @keyword = keyword
36
43
  @parent_proxy = parent
37
44
  @args = args
38
45
  @block = block
39
46
  @enabled = true
40
- @children = []
41
47
  post_add_content if @block.nil?
42
48
  end
43
-
49
+
44
50
  def post_add_content
45
- build_control
46
- super
51
+ if area_image?
52
+ @shapes = nil
53
+ super
54
+ if @parent_proxy.nil? && AreaProxy.current_area_draw_params
55
+ draw(AreaProxy.current_area_draw_params)
56
+ destroy
57
+ end
58
+ @content_added = true
59
+ else # image object not control
60
+ build_control
61
+ super
62
+ end
63
+ end
64
+
65
+ def file(value = nil)
66
+ if area_image?
67
+ if value.nil?
68
+ @args[0]
69
+ else
70
+ @args[0] = value
71
+ if @content_added
72
+ post_add_content
73
+ request_auto_redraw
74
+ end
75
+ end
76
+ end
47
77
  end
48
-
49
- def post_initialize_child(child)
50
- @children << child
78
+ alias file= file
79
+ alias set_file file
80
+
81
+ def width(value = nil)
82
+ if value.nil?
83
+ area_image? ? @args[1] : @args[0]
84
+ else
85
+ if area_image?
86
+ @args[1] = value
87
+ if @content_added
88
+ post_add_content
89
+ request_auto_redraw
90
+ end
91
+ else
92
+ @args[0] = value
93
+ end
94
+ end
95
+ end
96
+ alias width= width
97
+ alias set_width width
98
+
99
+ def height(value = nil)
100
+ if value.nil?
101
+ area_image? ? @args[2] : @args[1]
102
+ else
103
+ if area_image?
104
+ @args[2] = value
105
+ if @content_added
106
+ post_add_content
107
+ request_auto_redraw
108
+ end
109
+ else
110
+ @args[1] = value
111
+ end
112
+ end
113
+ end
114
+ alias height= height
115
+ alias set_height height
116
+
117
+ def redraw
118
+ @parent_proxy&.redraw
119
+ end
120
+
121
+ def request_auto_redraw
122
+ @parent_proxy&.request_auto_redraw if area_image?
123
+ end
124
+
125
+ def draw(area_draw_params)
126
+ if @shapes.nil?
127
+ load_image
128
+ parse_pixels
129
+ calculate_shapes
130
+ end
131
+ ImagePathRenderer.new(@parent_proxy, @shapes).render
132
+ end
133
+
134
+ def area_image?
135
+ @parent_proxy&.is_a?(AreaProxy) or
136
+ AreaProxy.current_area_draw_params or
137
+ @args[0].is_a?(String) # first arg is file
138
+ end
139
+
140
+ def destroy
141
+ @parent_proxy&.children&.delete(self)
142
+ ControlProxy.control_proxies.delete(self)
51
143
  end
52
144
 
53
145
  private
54
146
 
55
147
  def build_control
56
- @args = [@children.first.args[1], @children.first.args[2]] if @children.size == 1 && (@args[0].nil? || @args[1].nil?)
57
- super
58
- @libui.tap do
59
- @children.each {|child| child&.send(:build_control) }
148
+ unless area_image? # image object
149
+ @args = [@children.first.args[1], @children.first.args[2]] if @children.size == 1 && (@args[0].nil? || @args[1].nil?)
150
+ super
151
+ @libui.tap do
152
+ @children.each {|child| child&.send(:build_control) }
153
+ end
154
+ end
155
+ end
156
+
157
+ def load_image
158
+ require 'chunky_png'
159
+ f = File.open(file)
160
+ canvas = ChunkyPNG::Canvas.from_io(f)
161
+ f.close
162
+ canvas.resample_nearest_neighbor!(width, height) if width && height
163
+ @data = canvas.to_rgba_stream
164
+ self.width = canvas.width
165
+ self.height = canvas.height
166
+ end
167
+
168
+ def parse_pixels
169
+ @pixels = height.times.map do |y|
170
+ width.times.map do |x|
171
+ r = @data[(y*width + x)*4].ord
172
+ g = @data[(y*width + x)*4 + 1].ord
173
+ b = @data[(y*width + x)*4 + 2].ord
174
+ a = @data[(y*width + x)*4 + 3].ord
175
+ {x: x, y: y, color: {r: r, g: g, b: b, a: a}}
176
+ end
177
+ end.flatten
178
+ end
179
+
180
+ def calculate_shapes
181
+ @shapes = []
182
+ original_pixels = @pixels.dup
183
+ indexed_original_pixels = Hash[original_pixels.each_with_index.to_a]
184
+ @pixels.each do |pixel|
185
+ index = indexed_original_pixels[pixel]
186
+ @rectangle_start_x ||= pixel[:x]
187
+ @rectangle_width ||= 1
188
+ if pixel[:x] < width - 1 && pixel[:color] == original_pixels[index + 1][:color]
189
+ @rectangle_width += 1
190
+ else
191
+ if pixel[:x] > 0 && pixel[:color] == original_pixels[index - 1][:color]
192
+ @shapes << {x: @rectangle_start_x, y: pixel[:y], width: @rectangle_width, height: 1, color: pixel[:color]}
193
+ else
194
+ @shapes << {x: pixel[:x], y: pixel[:y], width: 1, height: 1, color: pixel[:color]}
195
+ end
196
+ @rectangle_width = 1
197
+ @rectangle_start_x = pixel[:x] == width - 1 ? 0 : pixel[:x] + 1
198
+ end
60
199
  end
200
+ @shapes
61
201
  end
62
202
  end
63
203
  end
@@ -45,7 +45,7 @@ module Glimmer
45
45
 
46
46
  def destroy
47
47
  super
48
- ControlProxy.image_proxies.each { |image_proxy| ::LibUI.free_image(image_proxy.libui) }
48
+ ControlProxy.image_proxies.each { |image_proxy| ::LibUI.free_image(image_proxy.libui) unless image_proxy.area_image? }
49
49
  @on_destroy_procs&.each { |on_destroy_proc| on_destroy_proc.call(self)}
50
50
  end
51
51
 
@@ -27,8 +27,8 @@ module Glimmer
27
27
  class ControlProxy
28
28
  class << self
29
29
  def exists?(keyword)
30
- ::LibUI.respond_to?("new_#{keyword}") ||
31
- ::LibUI.respond_to?(keyword) ||
30
+ ::LibUI.respond_to?("new_#{keyword}") or
31
+ ::LibUI.respond_to?(keyword) or
32
32
  descendant_keyword_constant_map.keys.include?(keyword)
33
33
  end
34
34
 
@@ -207,11 +207,11 @@ module Glimmer
207
207
  end
208
208
 
209
209
  def respond_to_libui?(method_name, *args, &block)
210
- ::LibUI.respond_to?("control_#{method_name}") ||
211
- (::LibUI.respond_to?("control_#{method_name.to_s.sub(/\?$/, '')}") && BOOLEAN_PROPERTIES.include?(method_name.to_s.sub(/\?$/, '')) ) ||
212
- ::LibUI.respond_to?("control_set_#{method_name.to_s.sub(/=$/, '')}") ||
213
- ::LibUI.respond_to?("#{libui_api_keyword}_#{method_name}") ||
214
- (::LibUI.respond_to?("#{libui_api_keyword}_#{method_name.to_s.sub(/\?$/, '')}") && BOOLEAN_PROPERTIES.include?(method_name.to_s.sub(/\?$/, '')) ) ||
210
+ ::LibUI.respond_to?("control_#{method_name}") or
211
+ (::LibUI.respond_to?("control_#{method_name.to_s.sub(/\?$/, '')}") && BOOLEAN_PROPERTIES.include?(method_name.to_s.sub(/\?$/, '')) ) or
212
+ ::LibUI.respond_to?("control_set_#{method_name.to_s.sub(/=$/, '')}") or
213
+ ::LibUI.respond_to?("#{libui_api_keyword}_#{method_name}") or
214
+ (::LibUI.respond_to?("#{libui_api_keyword}_#{method_name.to_s.sub(/\?$/, '')}") && BOOLEAN_PROPERTIES.include?(method_name.to_s.sub(/\?$/, '')) ) or
215
215
  ::LibUI.respond_to?("#{libui_api_keyword}_set_#{method_name.to_s.sub(/=$/, '')}")
216
216
  end
217
217
 
@@ -0,0 +1,30 @@
1
+ module Glimmer
2
+ module LibUI
3
+ class ImagePathRenderer
4
+ include Glimmer
5
+
6
+ def initialize(area_proxy, shapes)
7
+ @area_proxy = area_proxy
8
+ @shapes = shapes
9
+ end
10
+
11
+ def render
12
+ work = Proc.new do
13
+ @shapes.each do |shape|
14
+ path {
15
+ rectangle(shape[:x], shape[:y], shape[:width], shape[:height])
16
+
17
+ fill shape[:color]
18
+ }
19
+ end
20
+ end
21
+ if @area_proxy.nil?
22
+ # Ensure it renders without a parent
23
+ Glimmer::DSL::Engine.add_content(nil, Glimmer::DSL::Libui::ControlExpression.new, 'image', &work)
24
+ else
25
+ work.call
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer-dsl-libui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.24
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-14 00:00:00.000000000 Z
11
+ date: 2021-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glimmer
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 2.4.0
19
+ version: 2.4.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 2.4.0
26
+ version: 2.4.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: os
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -72,6 +72,20 @@ dependencies:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
74
  version: 0.0.12
75
+ - !ruby/object:Gem::Dependency
76
+ name: chunky_png
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 1.4.0
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: 1.4.0
75
89
  - !ruby/object:Gem::Dependency
76
90
  name: juwelier
77
91
  requirement: !ruby/object:Gem::Requirement
@@ -221,6 +235,11 @@ files:
221
235
  - examples/basic_draw_text.rb
222
236
  - examples/basic_draw_text2.rb
223
237
  - examples/basic_entry.rb
238
+ - examples/basic_image.rb
239
+ - examples/basic_image2.rb
240
+ - examples/basic_image3.rb
241
+ - examples/basic_image4.rb
242
+ - examples/basic_image5.rb
224
243
  - examples/basic_table.rb
225
244
  - examples/basic_table_button.rb
226
245
  - examples/basic_table_checkbox.rb
@@ -347,6 +366,7 @@ files:
347
366
  - lib/glimmer/libui/control_proxy/transformable.rb
348
367
  - lib/glimmer/libui/control_proxy/triple_column.rb
349
368
  - lib/glimmer/libui/control_proxy/window_proxy.rb
369
+ - lib/glimmer/libui/image_path_renderer.rb
350
370
  - lib/glimmer/libui/parent.rb
351
371
  - lib/glimmer/libui/shape.rb
352
372
  - lib/glimmer/libui/shape/arc.rb