glimmer 2.2.0 → 2.4.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: d61c8e3da07109cf0edf0df46e0937a862b3e80007a12da8050c2415481cbb6e
4
- data.tar.gz: c618e4c0b024a9da219fbe78c263a442981d3d0a40f8a13bb7639edf50310e09
3
+ metadata.gz: f6a8921b091e770dd1acb0528bdf8dad0a63e79290ffb457325abf261b4ac8ca
4
+ data.tar.gz: 4d530d648577640a25943fb55f61918d352f30e2007f7426b739b877cd6a44a0
5
5
  SHA512:
6
- metadata.gz: c1b3b5d7aa04427f0e1188e0cd7e3855d517c0adfb6c561160c465f043685c48a651df8fde9eb05e31b84578a1986182422e49bb329fd35b9f0cd623fac4bc77
7
- data.tar.gz: 5b4177d4ae038eefd1c210e8054f92ebe716b1a7edb2b40e94466ee2ba35aaafced1320128e75603f156ed3a7cdc56db200fd4369916dc0862f44fbd7ac12760
6
+ metadata.gz: 68f5e3c05e2381a6f4855cec0832cc983fc74d0dcf27649efaf9e92dd8cf7aa69f9e171494ee1ab17a6a8d68ce0d71cb9ef61521290d999eaa70ad3915b989f2
7
+ data.tar.gz: 6b3af9b3d411bed16cc9c9f2f227394675fd255098d64729424e31dfdbf1b7e75d8e3aac102016f31c8b729978e33b44fbca1b0dbecec1d1acffea3651dbc468
data/CHANGELOG.md CHANGED
@@ -3,6 +3,28 @@
3
3
  Related Change Logs:
4
4
  - [glimmer-dsl-swt/CHANGELOG.md](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/CHANGELOG.md)
5
5
 
6
+ ### 2.4.0
7
+
8
+ - Support passing arbitrary options to `Observer` `#observe` (`#register`) method (not just properties, like `recursive: true` for example)
9
+ - Make observing nested arrays within an array optional with `recursive: true` option
10
+ - Make `Observer` `dependents` collection rely on Concurrent::Hash when available
11
+ - Fix `Observer` cleanup of registrations and dependents on `#deregister` (was not cleaning up everything because observables were changing `#hash` value on content change and slipping through the cracks as a result)
12
+
13
+ ### 2.3.0
14
+
15
+ - Have observing `ObservableArray` automatically notice if any values are nested arrays and observe them for micro-changes (recursively all the way down)
16
+
17
+ ### 2.2.2
18
+
19
+ - Support auto-notifying on `ObservableArray#replace` observed changes
20
+ - Support auto-notifying on `ObservableArray#filter!` observed changes explicitly (even if `filter!` aliases `select!`)
21
+ - Support auto-notifying on `ObservableHash#filter!` observed changes explicitly (even if `filter!` aliases `select!`)
22
+
23
+ ### 2.2.1
24
+
25
+ - Have `ObservableHash` call `notify_observers(key)` upon performing all mutation operations (as expected): `delete`, `delete_if`, `filter!`, `select!`, `keep_if`, `reject!`, `merge!`, `replace`, `shift`, `transform_keys!`, `transform_values!`
26
+ - Fix issue with `Glimmer::DataBinding::Observer.proc {|new_value| ...}.observe(array)` resulting in new_value coming across as `nil when the array is updated
27
+
6
28
  ### 2.2.0
7
29
 
8
30
  - New `Glimmer::DataBinding::ObservableHash` support for observing `hash[key]=value` mutations
data/README.md CHANGED
@@ -210,7 +210,7 @@ end
210
210
  ### Setup
211
211
 
212
212
  Follow these steps to author a [Glimmer](https://rubygems.org/gems/glimmer) DSL:
213
- - Add `gem 'glimmer', '~> 2.2.0'` to `Gemfile` and run `bundle` or run `gem install glimmer -v2.2.0` and add `require 'glimmer'`
213
+ - Add `gem 'glimmer', '~> 2.4.0'` to `Gemfile` and run `bundle` or run `gem install glimmer -v2.4.0` and add `require 'glimmer'`
214
214
  - Create `glimmer/dsl/[dsl_name]/dsl.rb`, which requires and adds all dynamic expressions for the [dsl_name] Glimmer DSL module as per the code shown in the previous section (or [Official DSLs](#official-dsls) as examples)
215
215
  - Create `glimmer/dsl/[dsl_name]/[expresion_name]_expresion.rb` for every [expresion_name] expression needed, whether dynamic or static
216
216
 
@@ -856,7 +856,7 @@ Glimmer app:
856
856
 
857
857
  ![glimmer dsl tk screenshot sample hello world](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-tk/master/images/glimmer-dsl-tk-screenshot-sample-hello-world.png)
858
858
 
859
- ###### Hello, Tab!
859
+ ###### Hello, Notebook!
860
860
 
861
861
  Glimmer code (from [samples/hello/hello_tab.rb](https://github.com/AndyObtiva/glimmer-dsl-tk/blob/master/samples/hello/hello_tab.rb)):
862
862
 
@@ -864,7 +864,7 @@ Glimmer code (from [samples/hello/hello_tab.rb](https://github.com/AndyObtiva/gl
864
864
  include Glimmer
865
865
 
866
866
  root {
867
- title 'Hello, Tab!'
867
+ title 'Hello, Notebook!'
868
868
 
869
869
  notebook {
870
870
  frame(text: 'English') {
@@ -885,13 +885,13 @@ root {
885
885
  Run (with the [glimmer-dsl-tk](https://rubygems.org/gems/glimmer-dsl-tk) gem installed):
886
886
 
887
887
  ```
888
- ruby -r glimmer-dsl-tk -e "require '../samples/hello/hello_tab.rb'"
888
+ ruby -r glimmer-dsl-tk -e "require '../samples/hello/hello_notebook.rb'"
889
889
  ```
890
890
 
891
891
  Glimmer app:
892
892
 
893
- ![glimmer dsl tk screenshot sample hello tab English](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-tk/master/images/glimmer-dsl-tk-screenshot-sample-hello-tab-english.png)
894
- ![glimmer dsl tk screenshot sample hello tab French](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-tk/master/images/glimmer-dsl-tk-screenshot-sample-hello-tab-french.png)
893
+ ![glimmer dsl tk screenshot sample hello notebook English](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-tk/master/images/glimmer-dsl-tk-screenshot-sample-hello-notebook-english.png)
894
+ ![glimmer dsl tk screenshot sample hello notebook French](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-tk/master/images/glimmer-dsl-tk-screenshot-sample-hello-notebook-french.png)
895
895
 
896
896
  ###### Hello, Combo!
897
897
 
@@ -900,7 +900,7 @@ Glimmer code (from [samples/hello/hello_combo.rb](https://github.com/AndyObtiva/
900
900
  ```ruby
901
901
  # ... more code precedes
902
902
  root {
903
- title 'Hello, Combo!'
903
+ title 'Hello, Combobox!'
904
904
 
905
905
  combobox { |proxy|
906
906
  state 'readonly'
@@ -920,13 +920,13 @@ root {
920
920
  Run (with the [glimmer-dsl-tk](https://rubygems.org/gems/glimmer-dsl-tk) gem installed):
921
921
 
922
922
  ```
923
- ruby -r glimmer-dsl-tk -e "require '../samples/hello/hello_combo.rb'"
923
+ ruby -r glimmer-dsl-tk -e "require '../samples/hello/hello_combobox.rb'"
924
924
  ```
925
925
 
926
926
  Glimmer app:
927
927
 
928
- ![glimmer dsl tk screenshot sample hello combo](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-tk/master/images/glimmer-dsl-tk-screenshot-sample-hello-combo.png)
929
- ![glimmer dsl tk screenshot sample hello combo dropdown](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-tk/master/images/glimmer-dsl-tk-screenshot-sample-hello-combo-dropdown.png)
928
+ ![glimmer dsl tk screenshot sample hello combo](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-tk/master/images/glimmer-dsl-tk-screenshot-sample-hello-combobox.png)
929
+ ![glimmer dsl tk screenshot sample hello combo dropdown](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-tk/master/images/glimmer-dsl-tk-screenshot-sample-hello-combobox-dropdown.png)
930
930
 
931
931
  #### Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development GUI Library)
932
932
 
@@ -972,10 +972,10 @@ require 'glimmer-dsl-libui'
972
972
 
973
973
  include Glimmer
974
974
 
975
- window('hello world', 300, 200) { |w|
975
+ window('hello world', 300, 200) {
976
976
  button('Button') {
977
977
  on_clicked do
978
- msg_box(w, 'Information', 'You clicked the button')
978
+ msg_box('Information', 'You clicked the button')
979
979
  end
980
980
  }
981
981
 
@@ -995,223 +995,161 @@ Linux
995
995
  ![glimmer-dsl-libui-linux-basic-button.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-linux-basic-button.png)
996
996
  ![glimmer-dsl-libui-linux-basic-button-msg-box.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-linux-basic-button-msg-box.png)
997
997
 
998
- ###### Control Gallery
998
+ ###### Basic Table Progress Bar
999
999
 
1000
1000
  ```ruby
1001
1001
  require 'glimmer-dsl-libui'
1002
1002
 
1003
1003
  include Glimmer
1004
1004
 
1005
- menu('File') {
1006
- menu_item('Open') {
1007
- on_clicked do
1008
- file = open_file(MAIN_WINDOW)
1009
- puts file unless file.nil?
1010
- end
1011
- }
1012
-
1013
- menu_item('Save') {
1014
- on_clicked do
1015
- file = save_file(MAIN_WINDOW)
1016
- puts file unless file.nil?
1017
- end
1018
- }
1019
-
1020
- quit_menu_item {
1021
- on_clicked do
1022
- puts 'Bye Bye'
1023
- end
1024
- }
1025
-
1026
- preferences_menu_item # Can optionally contain an on_clicked listener
1027
- }
1028
-
1029
- menu('Edit') {
1030
- check_menu_item('Checkable Item_')
1031
- separator_menu_item
1032
- menu_item('Disabled Item_') {
1033
- enabled false
1034
- }
1035
- }
1036
-
1037
- menu('Help') {
1038
- menu_item('Help')
1039
-
1040
- about_menu_item # Can optionally contain an on_clicked listener
1041
- }
1005
+ data = [
1006
+ ['task 1', 0],
1007
+ ['task 2', 15],
1008
+ ['task 3', 100],
1009
+ ['task 4', 75],
1010
+ ['task 5', -1],
1011
+ ]
1042
1012
 
1043
- MAIN_WINDOW = window('Control Gallery', 600, 500) {
1044
- margined true
1045
-
1046
- on_closing do
1047
- puts 'Bye Bye'
1048
- end
1049
-
1013
+ window('Task Progress', 300, 200) {
1050
1014
  vertical_box {
1051
- horizontal_box {
1052
- group('Basic Controls') {
1053
- vertical_box {
1054
- button('Button') {
1055
- stretchy false
1056
-
1057
- on_clicked do
1058
- msg_box(MAIN_WINDOW, 'Information', 'You clicked the button')
1059
- end
1060
- }
1061
-
1062
- checkbox('Checkbox') {
1063
- stretchy false
1064
-
1065
- on_toggled do |c|
1066
- checked = c.checked == 1
1067
- MAIN_WINDOW.title = "Checkbox is #{checked}"
1068
- c.text = "I am the checkbox (#{checked})"
1069
- end
1070
- }
1071
-
1072
- label('Label') { stretchy false }
1073
-
1074
- horizontal_separator { stretchy false }
1075
-
1076
- date_picker { stretchy false }
1077
-
1078
- time_picker { stretchy false }
1079
-
1080
- date_time_picker { stretchy false }
1081
-
1082
- font_button { stretchy false }
1083
-
1084
- color_button { stretchy false }
1085
- }
1086
- }
1087
-
1088
- vertical_box {
1089
- group('Numbers') {
1090
- stretchy false
1091
-
1092
- vertical_box {
1093
- spinbox(0, 100) {
1094
- stretchy false
1095
- value 42
1096
-
1097
- on_changed do |s|
1098
- puts "New Spinbox value: #{s.value}"
1099
- end
1100
- }
1101
-
1102
- slider(0, 100) {
1103
- stretchy false
1104
-
1105
- on_changed do |s|
1106
- v = s.value
1107
- puts "New Slider value: #{v}"
1108
- @progress_bar.value = v
1109
- end
1110
- }
1111
-
1112
- @progress_bar = progress_bar { stretchy false }
1113
- }
1114
- }
1115
-
1116
- group('Lists') {
1117
- stretchy false
1118
-
1119
- vertical_box {
1120
- combobox {
1121
- stretchy false
1122
- items 'combobox Item 1', 'combobox Item 2', 'combobox Item 3' # also accepts a single array argument
1123
-
1124
- on_selected do |c|
1125
- puts "New combobox selection: #{c.selected}"
1126
- end
1127
- }
1128
-
1129
- editable_combobox {
1130
- stretchy false
1131
- items 'Editable Item 1', 'Editable Item 2', 'Editable Item 3' # also accepts a single array argument
1132
- }
1133
-
1134
- radio_buttons {
1135
- items 'Radio Button 1', 'Radio Button 2', 'Radio Button 3' # also accepts a single array argument
1136
- }
1137
- }
1138
- }
1139
-
1140
- tab {
1141
- tab_item('Page 1') {
1142
- horizontal_box {
1143
- entry {
1144
- text 'Please enter your feelings'
1015
+ table {
1016
+ text_column('Task')
1017
+ progress_bar_column('Progress')
1145
1018
 
1146
- on_changed do |e|
1147
- puts "Current textbox data: '#{e.text}'"
1148
- end
1149
- }
1150
- }
1151
- }
1152
-
1153
- tab_item('Page 2') {
1154
- horizontal_box
1155
- }
1156
-
1157
- tab_item('Page 3') {
1158
- horizontal_box
1159
- }
1160
- }
1161
- }
1019
+ cell_rows data # implicit data-binding
1020
+ }
1021
+
1022
+ button('Mark All As Done') {
1023
+ stretchy false
1024
+
1025
+ on_clicked do
1026
+ data.each_with_index do |row_data, row|
1027
+ data[row] = [row_data[0], 100] # automatically updates table due to implicit data-binding
1028
+ end
1029
+ end
1162
1030
  }
1163
1031
  }
1164
- }
1165
-
1166
- MAIN_WINDOW.show
1032
+ }.show
1167
1033
  ```
1168
1034
 
1169
1035
  Mac
1170
1036
 
1171
- ![glimmer-dsl-libui-mac-control-gallery.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-mac-control-gallery.png)
1037
+ ![glimmer-dsl-libui-mac-basic-table-progress-bar.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-mac-basic-table-progress-bar.png)
1172
1038
 
1173
1039
  Linux
1174
1040
 
1175
- ![glimmer-dsl-libui-linux-control-gallery.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-linux-control-gallery.png)
1041
+ ![glimmer-dsl-libui-linux-basic-table-progress-bar.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-linux-basic-table-progress-bar.png)
1176
1042
 
1177
- ###### Basic Table
1043
+ ###### Area Gallery
1178
1044
 
1179
1045
  ```ruby
1180
1046
  require 'glimmer-dsl-libui'
1181
1047
 
1182
1048
  include Glimmer
1183
1049
 
1184
- data = [
1185
- %w[cat meow],
1186
- %w[dog woof],
1187
- %w[chicken cock-a-doodle-doo],
1188
- %w[hourse neigh],
1189
- %w[cow moo]
1190
- ]
1050
+ window('Area Gallery', 400, 400) {
1051
+ area {
1052
+ path { # declarative stable path
1053
+ square(0, 0, 100)
1054
+ square(100, 100, 400)
1055
+
1056
+ fill r: 102, g: 102, b: 204
1057
+ }
1058
+ path { # declarative stable path
1059
+ rectangle(0, 100, 100, 400)
1060
+ rectangle(100, 0, 400, 100)
1061
+
1062
+ fill r: 204, g: 102, b: 204
1063
+ }
1064
+ path { # declarative stable path
1065
+ figure(100, 100) {
1066
+ line(100, 400)
1067
+ line(400, 100)
1068
+ line(400, 400)
1191
1069
 
1192
- window('Animal sounds', 300, 200) {
1193
- horizontal_box {
1194
- table {
1195
- text_column('Animal')
1196
- text_column('Description')
1070
+ closed true
1071
+ }
1197
1072
 
1198
- cell_rows data
1073
+ fill r: 202, g: 102, b: 104, a: 0.5
1074
+ stroke r: 0, g: 0, b: 0
1199
1075
  }
1076
+ path { # declarative stable path
1077
+ figure(0, 0) {
1078
+ bezier(200, 100, 100, 200, 400, 100)
1079
+ bezier(300, 100, 100, 300, 100, 400)
1080
+ bezier(100, 300, 300, 100, 400, 400)
1081
+
1082
+ closed true
1083
+ }
1084
+
1085
+ fill r: 202, g: 102, b: 204, a: 0.5
1086
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
1087
+ }
1088
+ path { # declarative stable path
1089
+ arc(200, 200, 90, 0, 360, false)
1090
+
1091
+ fill r: 202, g: 102, b: 204, a: 0.5
1092
+ stroke r: 0, g: 0, b: 0, thickness: 2
1093
+ }
1094
+
1095
+ on_mouse_event do |area_mouse_event|
1096
+ p area_mouse_event
1097
+ end
1098
+
1099
+ on_mouse_moved do |area_mouse_event|
1100
+ puts 'moved'
1101
+ end
1102
+
1103
+ on_mouse_down do |area_mouse_event|
1104
+ puts 'mouse down'
1105
+ end
1106
+
1107
+ on_mouse_up do |area_mouse_event|
1108
+ puts 'mouse up'
1109
+ end
1110
+
1111
+ on_mouse_drag_started do |area_mouse_event|
1112
+ puts 'drag started'
1113
+ end
1114
+
1115
+ on_mouse_dragged do |area_mouse_event|
1116
+ puts 'dragged'
1117
+ end
1118
+
1119
+ on_mouse_dropped do |area_mouse_event|
1120
+ puts 'dropped'
1121
+ end
1122
+
1123
+ on_mouse_entered do
1124
+ puts 'entered'
1125
+ end
1126
+
1127
+ on_mouse_exited do
1128
+ puts 'exited'
1129
+ end
1130
+
1131
+ on_key_event do |area_key_event|
1132
+ p area_key_event
1133
+ end
1134
+
1135
+ on_key_up do |area_key_event|
1136
+ puts 'key up'
1137
+ end
1138
+
1139
+ on_key_down do |area_key_event|
1140
+ puts 'key down'
1141
+ end
1200
1142
  }
1201
-
1202
- on_closing do
1203
- puts 'Bye Bye'
1204
- end
1205
1143
  }.show
1206
1144
  ```
1207
1145
 
1208
1146
  Mac
1209
1147
 
1210
- ![glimmer-dsl-libui-mac-basic-table.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-mac-basic-table.png)
1148
+ ![glimmer-dsl-libui-mac-area-gallery.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-mac-area-gallery.png)
1211
1149
 
1212
1150
  Linux
1213
1151
 
1214
- ![glimmer-dsl-libui-linux-basic-table.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-linux-basic-table.png)
1152
+ ![glimmer-dsl-libui-linux-area-gallery.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-linux-area-gallery.png)
1215
1153
 
1216
1154
  ## Data-Binding Library
1217
1155
 
@@ -1225,8 +1163,8 @@ These are the main classes concerning data-binding:
1225
1163
  - `Glimmer::DataBinding::Observer`: Provides general observer support including unique registration and deregistration for cleanup and prevention of memory leaks. Main methods concerned are: `call`, `register` (alias: `observe`), and `unregister` (alias: `unobserve` or `deregister`)
1226
1164
  - `Glimmer::DataBinding::Observable`: General super-module for all observables. Main methods concerned are: `add_observer` and `remove_observer`
1227
1165
  - `Glimmer::DataBinding::ObservableModel`: Mixin module for any observable model with observable attributes. In addition to `Observable` methods, it has a `notify_observers` method to be called when changes occur. It automatically enhances all attribute setters (ending with `=`) to notify observers on changes. Also, it automatically handles observing array attributes using `ObservableArray` appropriately so they would notify observers upon array mutation changes.
1228
- - `Glimmer::DataBinding::ObservableArray`: Mixin module for any observable array collection that automatically handles notifying observers upon performing array mutation operations (e.g. `push`, `select!`, or `delete`)
1229
- - `Glimmer::DataBinding::ObservableHash`: Mixin module for any observable hash that automatically handles notifying observers upon performing array mutation operation (e.g. `hash[key]=value`)
1166
+ - `Glimmer::DataBinding::ObservableArray`: Mixin module for any observable array collection that automatically handles notifying observers upon performing array mutation operations (e.g. `push`, `select!`, or `delete`) recursively (meaning if an array contained arrays and they changed, observers are notified). Accepts `recursive: true` option in `add_observer` method to recursively observe nested arrays all the way down.
1167
+ - `Glimmer::DataBinding::ObservableHash`: Mixin module for any observable hash that automatically handles notifying observers upon performing hash mutation operations (e.g. `hash[key]=value`, `select!`, `merge!`)
1230
1168
  - `Glimmer::DataBinding::ModelBinding`: a higher-level abstraction that relies on all the other observer/observable classes to support basic data-binding, nested data-binding, and computed data-binding
1231
1169
  - `Glimmer::DataBinding::Shine`: enables highly intuitive and visually expressive syntax to perform bidirectional (two-way) data-binding with `<=>` and unidirectional (one-way) data-binding with `<=`
1232
1170
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.2.0
1
+ 2.4.0
data/glimmer.gemspec CHANGED
@@ -2,17 +2,17 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: glimmer 2.2.0 ruby lib
5
+ # stub: glimmer 2.4.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "glimmer".freeze
9
- s.version = "2.2.0"
9
+ s.version = "2.4.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["AndyMaleh".freeze]
14
- s.date = "2021-09-28"
15
- s.description = "Glimmer is a Ruby DSL Framework for Ruby GUI and More, consisting of a DSL Engine and an Observable/Observer/Data-Binding Library. Used in the Glimmer DSL for SWT (JRuby Desktop Development GUI Framework), the Glimmer DSL for Tk (Ruby Desktop Development GUI Library), the Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps), the Glimmer DSL for XML (& HTML), and the Glimmer DSL for CSS.".freeze
14
+ s.date = "2021-10-15"
15
+ s.description = "Glimmer is a Ruby DSL Framework for Ruby GUI and More, consisting of a DSL Engine and an Observable / Observer / Data-Binding Library (including Observable Model, Observable Array, and Observable Hash). Used in Glimmer DSL for SWT (JRuby Desktop Development GUI Framework), Glimmer DSL for Tk (Ruby Desktop Development GUI Library), Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development GUI Library), Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps), Glimmer DSL for XML (& HTML), and Glimmer DSL for CSS.".freeze
16
16
  s.email = "andy.am@gmail.com".freeze
17
17
  s.extra_rdoc_files = [
18
18
  "CHANGELOG.md",
@@ -101,6 +101,10 @@ module Glimmer
101
101
  def computed_by
102
102
  Concurrent::Array.new([@binding_options[:computed_by]].flatten.compact)
103
103
  end
104
+
105
+ def observation_options
106
+ @binding_options.slice(:recursive)
107
+ end
104
108
 
105
109
  def nested_property_observers_for(observer)
106
110
  @nested_property_observers_collection ||= Concurrent::Hash.new
@@ -121,7 +125,7 @@ module Glimmer
121
125
  @nested_property_observers_collection[observer]
122
126
  end
123
127
 
124
- def add_observer(observer)
128
+ def add_observer(observer, extra_options = {})
125
129
  if computed?
126
130
  add_computed_observers(observer)
127
131
  elsif nested_property?
@@ -133,19 +137,20 @@ module Glimmer
133
137
  apply_processor(@binding_options[:after_read], converted_value)
134
138
  end
135
139
  end
136
- observer_registration = model_binding_observer.observe(model, property_name)
140
+ observer_registration = model_binding_observer.observe(model, property_name, observation_options)
137
141
  my_registration = observer.registration_for(self)
138
142
  observer.add_dependent(my_registration => observer_registration)
139
143
  end
140
144
  end
141
145
 
142
- def remove_observer(observer)
146
+ def remove_observer(observer, extra_options = {})
143
147
  if computed?
144
148
  @computed_model_bindings.each do |computed_model_binding|
145
149
  computed_observer_for(observer).unobserve(computed_model_binding)
146
150
  end
147
151
  @computed_observer_collection.delete(observer)
148
152
  elsif nested_property?
153
+ # No need to call remove_nested_observers(observer) (cleanup happens automatically indirectly when invoked through observer.unobserve(model_binding))
149
154
  nested_property_observers_for(observer).clear
150
155
  else
151
156
  observer.unobserve(model, property_name)
@@ -167,7 +172,7 @@ module Glimmer
167
172
 
168
173
  def add_computed_observers(observer)
169
174
  @computed_model_bindings.each do |computed_model_binding|
170
- observer_registration = computed_observer_for(observer).observe(computed_model_binding)
175
+ observer_registration = computed_observer_for(observer).observe(computed_model_binding, observation_options)
171
176
  my_registration = observer.registration_for(self)
172
177
  observer.add_dependent(my_registration => observer_registration)
173
178
  end
@@ -191,13 +196,13 @@ module Glimmer
191
196
  parent_property_name = nil if parent_property_name.to_s.start_with?('[')
192
197
  unless model.nil?
193
198
  # TODO figure out a way to deal with this more uniformly
194
- observer_registration = property_indexed?(property_name) ? nested_property_observer.observe(model) : nested_property_observer.observe(model, property_name)
195
- parent_registration = parent_observer.registration_for(parent_model, parent_property_name)
199
+ observer_registration = property_indexed?(property_name) ? nested_property_observer.observe(model, observation_options) : nested_property_observer.observe(model, property_name, observation_options)
200
+ parent_registration = parent_observer.registration_for(parent_model, *[parent_property_name].compact)
196
201
  parent_observer.add_dependent(parent_registration => observer_registration)
197
202
  end
198
203
  end
199
204
  end
200
-
205
+
201
206
  def call(value, *extra_args)
202
207
  return if model.nil?
203
208
  converted_value = value
@@ -21,6 +21,7 @@
21
21
 
22
22
  require 'set'
23
23
  require 'glimmer/data_binding/observable'
24
+ require 'glimmer/data_binding/observer'
24
25
  require 'array_include_methods'
25
26
 
26
27
  using ArrayIncludeMethods
@@ -29,29 +30,64 @@ module Glimmer
29
30
  module DataBinding
30
31
  module ObservableArray
31
32
  include Observable
33
+
34
+ class Notifier
35
+ include Observer
36
+
37
+ attr_reader :observable_array
38
+
39
+ def initialize(observable_array)
40
+ @observable_array = observable_array
41
+ end
42
+
43
+ def call(new_value=nil, *extra_args)
44
+ @observable_array.notify_observers
45
+ end
46
+ end
32
47
 
33
- def add_observer(observer, *element_properties)
48
+ def add_observer(observer, *args)
49
+ options = args.last.is_a?(Hash) ? args.pop : {}
50
+ element_properties = args
34
51
  element_properties = element_properties.flatten.compact.uniq
35
52
  return observer if has_observer?(observer) && has_observer_element_properties?(observer, element_properties)
36
53
  property_observer_list << observer
37
54
  observer_element_properties[observer] = element_properties_for(observer) + Concurrent::Set.new(element_properties)
38
- each { |element| add_element_observer(element, observer) }
55
+ each { |element| add_element_observer(element, observer, options) }
39
56
  observer
40
57
  end
41
58
 
42
- def add_element_observers(element)
59
+ def add_element_observers(element, options = {})
43
60
  property_observer_list.each do |observer|
44
- add_element_observer(element, observer)
61
+ add_element_observer(element, observer, options)
45
62
  end
46
63
  end
47
64
 
48
- def add_element_observer(element, observer)
65
+ def add_element_observer(element, observer, options = {})
49
66
  element_properties_for(observer).each do |property|
50
- observer.observe(element, property)
67
+ observer.observe(element, property, options)
51
68
  end
69
+ ensure_array_object_observer(element, options) if options[:recursive] && element.is_a?(Array)
70
+ end
71
+
72
+ def ensure_array_object_observer(object, options)
73
+ return unless object&.is_a?(Array)
74
+ array_object_observer = array_object_observer_for(object)
75
+ array_observer_registration = array_object_observer.observe(object, options)
76
+ property_observer_list.each do |observer|
77
+ my_registration = observer.registration_for(self)
78
+ observer.add_dependent(my_registration => array_observer_registration)
79
+ end
80
+ end
81
+
82
+ def array_object_observer_for(object)
83
+ @array_object_observers ||= Concurrent::Hash.new
84
+ @array_object_observers[object] = Notifier.new(self) unless @array_object_observers.has_key?(object)
85
+ @array_object_observers[object]
52
86
  end
53
87
 
54
- def remove_observer(observer, *element_properties)
88
+ def remove_observer(observer, *args)
89
+ options = args.last.is_a?(Hash) ? args.pop : {}
90
+ element_properties = args
55
91
  element_properties = element_properties.flatten.compact.uniq
56
92
  if !element_properties.empty?
57
93
  old_element_properties = element_properties_for(observer)
@@ -76,6 +112,13 @@ module Glimmer
76
112
  element_properties_for(observer).each do |property|
77
113
  observer.unobserve(element, property)
78
114
  end
115
+ if element.is_a?(ObservableArray)
116
+ array_object_observer_for(element).unobserve(element)
117
+ element.property_observer_list.select {|o| o.respond_to?(:observable_array) && o.observable_array == self}.each do |o|
118
+ o.deregister_all_observables
119
+ @array_object_observers.reject! {|k, v| v == o}
120
+ end
121
+ end
79
122
  end
80
123
 
81
124
  def has_observer?(observer)
@@ -99,7 +142,7 @@ module Glimmer
99
142
  end
100
143
 
101
144
  def notify_observers
102
- property_observer_list.to_a.each(&:call)
145
+ property_observer_list.to_a.each { |o| o.call(self) }
103
146
  end
104
147
 
105
148
  def <<(element)
@@ -204,6 +247,7 @@ module Glimmer
204
247
  alias map! collect!
205
248
 
206
249
  def compact!
250
+ # TODO consider checking which exact indices changed and only notifying if there is a change
207
251
  super.tap { notify_observers }
208
252
  end
209
253
 
@@ -237,6 +281,21 @@ module Glimmer
237
281
  end
238
282
  end
239
283
 
284
+ def filter!(&block)
285
+ if block_given?
286
+ old_array = Array.new(self)
287
+ super(&block).tap do
288
+ (old_array - self).each do |old_value|
289
+ unregister_dependent_observers(old_value)
290
+ remove_element_observers(old_value)
291
+ end
292
+ notify_observers
293
+ end
294
+ else
295
+ super
296
+ end
297
+ end
298
+
240
299
  def shuffle!(hash = nil)
241
300
  (hash.nil? ? super() : super(random: hash[:random])).tap { notify_observers }
242
301
  end
@@ -294,6 +353,17 @@ module Glimmer
294
353
  end
295
354
  end
296
355
 
356
+ def replace(other_array)
357
+ old_array = Array.new(self)
358
+ super(other_array).tap do
359
+ (old_array - self).each do |old_value|
360
+ unregister_dependent_observers(old_value)
361
+ remove_element_observers(old_value)
362
+ end
363
+ notify_observers
364
+ end
365
+ end
366
+
297
367
  def unregister_dependent_observers(old_value)
298
368
  return unless old_value.is_a?(ObservableModel) || old_value.is_a?(ObservableArray)
299
369
  property_observer_list.each { |observer| observer.unregister_dependents_with_observable(observer.registration_for(self), old_value) }
@@ -62,14 +62,19 @@ module Glimmer
62
62
  end
63
63
  end
64
64
 
65
- def add_observer(observer, key = nil)
65
+ def add_observer(observer, key = nil, options = {})
66
+ if key.is_a?(Hash)
67
+ options = key
68
+ key = nil
69
+ end
66
70
  return observer if has_observer?(observer, key)
67
71
  key_observer_list(key) << observer
68
- add_key_writer_observer(key)
72
+ add_key_writer_observer(key, options)
69
73
  observer
70
74
  end
71
75
 
72
- def remove_observer(observer, key = nil)
76
+ def remove_observer(observer, key = nil, options = {})
77
+ old_value = self[key]
73
78
  if has_observer?(observer, key)
74
79
  key_observer_list(key).delete(observer)
75
80
  observer.unobserve(self, key)
@@ -115,12 +120,12 @@ module Glimmer
115
120
 
116
121
  def notify_observers(key)
117
122
  all_key_observer_list.to_a.each { |observer| observer.call(self[key], key) }
118
- (key_observer_list(key).to_a - all_key_observer_list.to_a).each { |observer| observer.call(self[key]) }
123
+ (key_observer_list(key).to_a - all_key_observer_list.to_a).each { |observer| observer.call(self[key], key) }
119
124
  end
120
125
 
121
- def add_key_writer_observer(key = nil)
122
- ensure_array_object_observer(key, self[key])
123
- ensure_hash_object_observer(key, self[key])
126
+ def add_key_writer_observer(key = nil, options)
127
+ ensure_array_object_observer(key, self[key], nil, options)
128
+ ensure_hash_object_observer(key, self[key], nil, options)
124
129
  begin
125
130
  method('__original__store')
126
131
  rescue
@@ -143,10 +148,11 @@ module Glimmer
143
148
  end
144
149
  alias deregister_dependent_observers unregister_dependent_observers
145
150
 
146
- def ensure_array_object_observer(key, object, old_object = nil)
151
+ def ensure_array_object_observer(key, object, old_object = nil, options = {})
152
+ options ||= {}
147
153
  return unless object&.is_a?(Array)
148
154
  array_object_observer = array_object_observer_for(key)
149
- array_observer_registration = array_object_observer.observe(object)
155
+ array_observer_registration = array_object_observer.observe(object, options)
150
156
  key_observer_list(key).each do |observer|
151
157
  my_registration = observer.registration_for(self, key) # TODO eliminate repetition
152
158
  observer.add_dependent(my_registration => array_observer_registration)
@@ -160,10 +166,11 @@ module Glimmer
160
166
  @array_object_observers[key]
161
167
  end
162
168
 
163
- def ensure_hash_object_observer(key, object, old_object = nil)
169
+ def ensure_hash_object_observer(key, object, old_object = nil, options = {})
170
+ options ||= {}
164
171
  return unless object&.is_a?(Hash)
165
172
  hash_object_observer = hash_object_observer_for(key)
166
- hash_observer_registration = hash_object_observer.observe(object)
173
+ hash_observer_registration = hash_object_observer.observe(object, options)
167
174
  key_observer_list(key).each do |observer|
168
175
  my_registration = observer.registration_for(self, key) # TODO eliminate repetition
169
176
  observer.add_dependent(my_registration => hash_observer_registration)
@@ -176,6 +183,200 @@ module Glimmer
176
183
  @hash_object_observers[key] = ObservableModel::Notifier.new(self, key) unless @hash_object_observers.has_key?(key)
177
184
  @hash_object_observers[key]
178
185
  end
186
+
187
+ def delete(key, &block)
188
+ old_value = self[key]
189
+ unless old_value.nil?
190
+ unregister_dependent_observers(key, old_value)
191
+ unregister_dependent_observers(nil, old_value)
192
+ end
193
+ super(key, &block).tap do
194
+ notify_observers(key) unless old_value.nil?
195
+ end
196
+ end
197
+
198
+ def delete_if(&block)
199
+ if block_given?
200
+ old_hash = self.dup
201
+ super(&block).tap do |new_hash|
202
+ deleted_keys = old_hash.keys - new_hash.keys
203
+ deleted_keys.each do |deleted_key|
204
+ deleted_value = old_hash[deleted_key]
205
+ unless deleted_value.nil?
206
+ unregister_dependent_observers(deleted_key, deleted_value)
207
+ unregister_dependent_observers(nil, deleted_value)
208
+ notify_observers(deleted_key)
209
+ end
210
+ end
211
+ end
212
+ else
213
+ super
214
+ end
215
+ end
216
+
217
+ def select!(&block)
218
+ if block_given?
219
+ old_hash = self.dup
220
+ super(&block).tap do |new_hash|
221
+ deleted_keys = old_hash.keys - new_hash.keys
222
+ deleted_keys.each do |deleted_key|
223
+ deleted_value = old_hash[deleted_key]
224
+ unless deleted_value.nil?
225
+ unregister_dependent_observers(deleted_key, deleted_value)
226
+ unregister_dependent_observers(nil, deleted_value)
227
+ notify_observers(deleted_key)
228
+ end
229
+ end
230
+ end
231
+ else
232
+ super
233
+ end
234
+ end
235
+
236
+ def filter!(&block)
237
+ if block_given?
238
+ old_hash = self.dup
239
+ super(&block).tap do |new_hash|
240
+ deleted_keys = old_hash.keys - new_hash.keys
241
+ deleted_keys.each do |deleted_key|
242
+ deleted_value = old_hash[deleted_key]
243
+ unless deleted_value.nil?
244
+ unregister_dependent_observers(deleted_key, deleted_value)
245
+ unregister_dependent_observers(nil, deleted_value)
246
+ notify_observers(deleted_key)
247
+ end
248
+ end
249
+ end
250
+ else
251
+ super
252
+ end
253
+ end
254
+
255
+ def keep_if(&block)
256
+ if block_given?
257
+ old_hash = self.dup
258
+ super(&block).tap do |new_hash|
259
+ deleted_keys = old_hash.keys - new_hash.keys
260
+ deleted_keys.each do |deleted_key|
261
+ deleted_value = old_hash[deleted_key]
262
+ unless deleted_value.nil?
263
+ unregister_dependent_observers(deleted_key, deleted_value)
264
+ unregister_dependent_observers(nil, deleted_value)
265
+ notify_observers(deleted_key)
266
+ end
267
+ end
268
+ end
269
+ else
270
+ super
271
+ end
272
+ end
273
+
274
+ def reject!(&block)
275
+ if block_given?
276
+ old_hash = self.dup
277
+ super(&block).tap do |new_hash|
278
+ deleted_keys = old_hash.keys - new_hash.keys
279
+ deleted_keys.each do |deleted_key|
280
+ deleted_value = old_hash[deleted_key]
281
+ unless deleted_value.nil?
282
+ unregister_dependent_observers(deleted_key, deleted_value)
283
+ unregister_dependent_observers(nil, deleted_value)
284
+ notify_observers(deleted_key)
285
+ end
286
+ end
287
+ end
288
+ else
289
+ super
290
+ end
291
+ end
292
+
293
+ def shift
294
+ old_hash = self.dup
295
+ super.tap do
296
+ new_hash = self
297
+ deleted_keys = old_hash.keys - new_hash.keys
298
+ deleted_keys.each do |deleted_key|
299
+ deleted_value = old_hash[deleted_key]
300
+ unless deleted_value.nil?
301
+ unregister_dependent_observers(deleted_key, deleted_value)
302
+ unregister_dependent_observers(nil, deleted_value)
303
+ notify_observers(deleted_key)
304
+ end
305
+ end
306
+ end
307
+ end
308
+
309
+ def merge!(*other_hashes, &block)
310
+ if other_hashes.empty?
311
+ super
312
+ else
313
+ old_hash = self.dup
314
+ super(*other_hashes, &block).tap do |new_hash|
315
+ changed_keys = other_hashes.map(&:keys).reduce(:+)
316
+ changed_keys.each do |changed_key|
317
+ old_value = old_hash[changed_key]
318
+ if new_hash[changed_key] != old_value
319
+ unregister_dependent_observers(changed_key, old_value)
320
+ unregister_dependent_observers(nil, old_value)
321
+ notify_observers(changed_key)
322
+ end
323
+ end
324
+ end
325
+ end
326
+ end
327
+
328
+ def replace(other_hash)
329
+ old_hash = self.dup
330
+ super(other_hash).tap do |new_hash|
331
+ changed_keys = old_hash.keys + new_hash.keys
332
+ changed_keys.each do |changed_key|
333
+ old_value = old_hash[changed_key]
334
+ if new_hash[changed_key] != old_value
335
+ unregister_dependent_observers(changed_key, old_value)
336
+ unregister_dependent_observers(nil, old_value)
337
+ notify_observers(changed_key)
338
+ end
339
+ end
340
+ end
341
+ end
342
+
343
+ def transform_keys!(hash2 = nil, &block)
344
+ if hash2.nil? && block.nil?
345
+ super
346
+ else
347
+ old_hash = self.dup
348
+ result = hash2.nil? ? super(&block) : super(hash2, &block)
349
+ result.tap do |new_hash|
350
+ changed_keys = old_hash.keys + new_hash.keys
351
+ changed_keys.each do |changed_key|
352
+ old_value = old_hash[changed_key]
353
+ if new_hash[changed_key] != old_value
354
+ unregister_dependent_observers(changed_key, old_value)
355
+ unregister_dependent_observers(nil, old_value)
356
+ notify_observers(changed_key)
357
+ end
358
+ end
359
+ end
360
+ end
361
+ end
362
+
363
+ def transform_values!(&block)
364
+ if block_given?
365
+ old_hash = self.dup
366
+ super(&block).tap do |new_hash|
367
+ new_hash.keys.each do |changed_key|
368
+ old_value = old_hash[changed_key]
369
+ if new_hash[changed_key] != old_value
370
+ unregister_dependent_observers(changed_key, old_value)
371
+ unregister_dependent_observers(nil, old_value)
372
+ notify_observers(changed_key)
373
+ end
374
+ end
375
+ end
376
+ else
377
+ super
378
+ end
379
+ end
179
380
  end
180
381
  end
181
382
  end
@@ -52,14 +52,14 @@ module Glimmer
52
52
  end
53
53
  end
54
54
 
55
- def add_observer(observer, property_name)
55
+ def add_observer(observer, property_name, options = {})
56
56
  return observer if has_observer?(observer, property_name)
57
57
  property_observer_list(property_name) << observer
58
- add_property_writer_observers(property_name)
58
+ add_property_writer_observers(property_name, options)
59
59
  observer
60
60
  end
61
61
 
62
- def remove_observer(observer, property_name)
62
+ def remove_observer(observer, property_name, options = {})
63
63
  if has_observer?(observer, property_name)
64
64
  property_observer_list(property_name).delete(observer)
65
65
  observer.unobserve(self, property_name)
@@ -103,11 +103,11 @@ module Glimmer
103
103
  property_observer_list(property_name).to_a.each { |observer| observer.call(send(property_name)) }
104
104
  end
105
105
 
106
- def add_property_writer_observers(property_name)
106
+ def add_property_writer_observers(property_name, options)
107
107
  property_writer_name = "#{property_name}="
108
108
  method(property_writer_name)
109
- ensure_array_object_observer(property_name, send(property_name))
110
- ensure_hash_object_observer(property_name, send(property_name))
109
+ ensure_array_object_observer(property_name, send(property_name), nil, options)
110
+ ensure_hash_object_observer(property_name, send(property_name), nil, options)
111
111
  begin
112
112
  method("__original__#{property_writer_name}")
113
113
  rescue
@@ -130,10 +130,11 @@ module Glimmer
130
130
  end
131
131
  alias deregister_dependent_observers unregister_dependent_observers
132
132
 
133
- def ensure_array_object_observer(property_name, object, old_object = nil)
133
+ def ensure_array_object_observer(property_name, object, old_object = nil, options = {})
134
+ options ||= {}
134
135
  return unless object&.is_a?(Array)
135
136
  array_object_observer = array_object_observer_for(property_name)
136
- array_observer_registration = array_object_observer.observe(object)
137
+ array_observer_registration = array_object_observer.observe(object, options)
137
138
  property_observer_list(property_name).each do |observer|
138
139
  my_registration = observer.registration_for(self, property_name) # TODO eliminate repetition
139
140
  observer.add_dependent(my_registration => array_observer_registration)
@@ -143,14 +144,15 @@ module Glimmer
143
144
 
144
145
  def array_object_observer_for(property_name)
145
146
  @array_object_observers ||= Concurrent::Hash.new
146
- @array_object_observers[property_name] = ObservableModel::Notifier.new(self, property_name) unless @array_object_observers.has_key?(property_name)
147
+ @array_object_observers[property_name] = Notifier.new(self, property_name) unless @array_object_observers.has_key?(property_name)
147
148
  @array_object_observers[property_name]
148
149
  end
149
150
 
150
- def ensure_hash_object_observer(property_name, object, old_object = nil)
151
+ def ensure_hash_object_observer(property_name, object, old_object = nil, options)
152
+ options ||= {}
151
153
  return unless object&.is_a?(Hash)
152
154
  hash_object_observer = hash_object_observer_for(property_name)
153
- hash_observer_registration = hash_object_observer.observe(object)
155
+ hash_observer_registration = hash_object_observer.observe(object, options)
154
156
  property_observer_list(property_name).each do |observer|
155
157
  my_registration = observer.registration_for(self, property_name) # TODO eliminate repetition
156
158
  observer.add_dependent(my_registration => hash_observer_registration)
@@ -160,7 +162,7 @@ module Glimmer
160
162
 
161
163
  def hash_object_observer_for(property_name)
162
164
  @hash_object_observers ||= Concurrent::Hash.new
163
- @hash_object_observers[property_name] = ObservableModel::Notifier.new(self, property_name) unless @hash_object_observers.has_key?(property_name)
165
+ @hash_object_observers[property_name] = Notifier.new(self, property_name) unless @hash_object_observers.has_key?(property_name)
164
166
  @hash_object_observers[property_name]
165
167
  end
166
168
  end
@@ -46,9 +46,9 @@ module Glimmer
46
46
  end
47
47
  end
48
48
 
49
- class Registration < Struct.new(:observer, :observable, :property, keyword_init: true)
49
+ class Registration < Struct.new(:observer, :observable, :args, keyword_init: true)
50
50
  def unregister
51
- observer.unobserve(observable, property)
51
+ observer.unobserve(observable, *args)
52
52
  end
53
53
  alias unobserve unregister
54
54
  alias deregister unregister
@@ -61,29 +61,31 @@ module Glimmer
61
61
  end
62
62
 
63
63
  def registrations
64
- @registrations ||= Concurrent::Set.new
64
+ @registrations ||= Concurrent::Hash.new
65
65
  end
66
66
 
67
- def registration_for(observable, property = nil)
68
- Registration.new(observer: self, observable: observable, property: property)
67
+ def registration_for(observable, *args)
68
+ args = compact_args(args)
69
+ registrations[[observable.object_id, args]] ||= Registration.new(observer: self, observable: observable, args: args)
69
70
  end
71
+ alias ensure_registration_for! registration_for
70
72
 
71
73
  # mapping of registrations to dependents
72
74
  # {[observable, property] => [[dependent, dependent_observable, dependent_property], ...]}
73
75
  def dependents
74
- @dependents ||= Hash.new
76
+ @dependents ||= Concurrent::Hash.new
75
77
  end
76
78
 
77
79
  def dependents_for(registration)
78
- dependents[registration] ||= Concurrent::Set.new
80
+ dependents[registration.object_id] ||= Concurrent::Set.new
79
81
  end
80
82
 
81
- # registers observer in an observable on a property (optional)
83
+ # registers observer in an observable on args usually containing a property and options (optional)
82
84
  # observer maintains registration list to unregister later
83
- def register(observable, property = nil)
85
+ def register(observable, *args)
84
86
  return if observable.nil?
85
87
  unless observable.is_a?(Observable)
86
- # TODO refactor code to be more smart/polymorphic/automated and honor open/closed principle
88
+ # TODO refactor code to be more smart/polymorphic/automated and honor open/closed principle (e.g. for SomeClass, search if there is ObservableSomeClass)
87
89
  if observable.is_a?(Array)
88
90
  observable.extend(ObservableArray)
89
91
  elsif observable.is_a?(Hash)
@@ -92,23 +94,23 @@ module Glimmer
92
94
  observable.extend(ObservableModel)
93
95
  end
94
96
  end
95
- observable.add_observer(*[self, property].compact)
96
- registration_for(observable, property).tap do |registration|
97
- self.registrations << registration
98
- end
97
+ args = compact_args(args)
98
+ observable.add_observer(self, *args)
99
+ ensure_registration_for!(observable, *args)
99
100
  end
100
101
  alias observe register
101
102
 
102
- def unregister(observable, property = nil)
103
+ def unregister(observable, *args)
103
104
  return unless observable.is_a?(Observable)
104
- # TODO optimize performance in the future via indexing and/or making a registration official object/class
105
- registration = registration_for(observable, property)
106
- dependents_for(registration).each do |dependent|
107
- dependent.unregister
108
- remove_dependent(registration => dependent)
109
- end
110
- registrations.delete(registration).tap do |registration|
111
- observable.remove_observer(*[self, property].compact)
105
+ args = compact_args(args)
106
+ registration = registration_for(observable, *args)
107
+ registrations.delete([observable.object_id, args])
108
+ registration.tap do |registration|
109
+ dependents_for(registration).each do |dependent|
110
+ remove_dependent(registration => dependent)
111
+ dependent.deregister if dependent != registration
112
+ end
113
+ observable.remove_observer(self, *args)
112
114
  end
113
115
  end
114
116
  alias unobserve unregister
@@ -118,14 +120,17 @@ module Glimmer
118
120
  thedependents = dependents_for(registration).select do |thedependent|
119
121
  thedependent.observable == dependent_observable
120
122
  end
121
- thedependents.each(&:unregister)
123
+ thedependents.each(&:deregister)
122
124
  end
123
125
  alias unobserve_dependents_with_observable unregister_dependents_with_observable
124
126
  alias deregister_dependents_with_observable unregister_dependents_with_observable
125
127
 
126
128
  # cleans up all registrations in observables
127
129
  def unregister_all_observables
128
- registrations.each(&:unregister)
130
+ registrations.values.dup.each do |registration|
131
+ registration.deregister
132
+ registrations.delete([registration.observable.object_id, registration.args])
133
+ end
129
134
  end
130
135
  alias unobserve_all_observables unregister_all_observables
131
136
  alias deregister_all_observables unregister_all_observables
@@ -140,12 +145,20 @@ module Glimmer
140
145
  def remove_dependent(parent_to_dependent_hash)
141
146
  registration = parent_to_dependent_hash.keys.first
142
147
  dependent = parent_to_dependent_hash.values.first
143
- dependents_for(registration).delete(dependent)
148
+ dependents_for(registration).delete(dependent).tap do
149
+ dependents.delete([registration.object_id]) if dependents_for(registration).empty?
150
+ end
144
151
  end
145
152
 
146
153
  def call(new_value = nil, *extra_args)
147
154
  raise Error, 'Not implemented!'
148
155
  end
156
+
157
+ def compact_args(args)
158
+ args = args[0...-1] if args.last == {}
159
+ args = args[0...-1] if args.last == []
160
+ args.compact
161
+ end
149
162
  end
150
163
  end
151
164
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - AndyMaleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-28 00:00:00.000000000 Z
11
+ date: 2021-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: array_include_methods
@@ -203,11 +203,12 @@ dependencies:
203
203
  - !ruby/object:Gem::Version
204
204
  version: '0'
205
205
  description: Glimmer is a Ruby DSL Framework for Ruby GUI and More, consisting of
206
- a DSL Engine and an Observable/Observer/Data-Binding Library. Used in the Glimmer
207
- DSL for SWT (JRuby Desktop Development GUI Framework), the Glimmer DSL for Tk (Ruby
208
- Desktop Development GUI Library), the Glimmer DSL for Opal (Pure Ruby Web GUI and
209
- Auto-Webifier of Desktop Apps), the Glimmer DSL for XML (& HTML), and the Glimmer
210
- DSL for CSS.
206
+ a DSL Engine and an Observable / Observer / Data-Binding Library (including Observable
207
+ Model, Observable Array, and Observable Hash). Used in Glimmer DSL for SWT (JRuby
208
+ Desktop Development GUI Framework), Glimmer DSL for Tk (Ruby Desktop Development
209
+ GUI Library), Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development
210
+ GUI Library), Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop
211
+ Apps), Glimmer DSL for XML (& HTML), and Glimmer DSL for CSS.
211
212
  email: andy.am@gmail.com
212
213
  executables: []
213
214
  extensions: []