glimmer-dsl-libui 0.2.24 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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