glimmer 2.1.5 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +143 -204
- data/VERSION +1 -1
- data/glimmer.gemspec +6 -5
- data/lib/glimmer/data_binding/model_binding.rb +1 -1
- data/lib/glimmer/data_binding/observable_array.rb +68 -1
- data/lib/glimmer/data_binding/observable_hash.rb +375 -0
- data/lib/glimmer/data_binding/observable_model.rb +22 -3
- data/lib/glimmer/data_binding/observer.rb +6 -3
- metadata +10 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04a3036ea66bf774c6c27cf8b248e5658df35ccc3a69998159e43a013d3489e0
|
4
|
+
data.tar.gz: 4241bfc406e54c13bbc4357918bf2bfed5a353f63f5200eea6ed60e0f7c84281
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ff74a70f8137ce7967d36e47a191ebdf8efbd67e15724449dd9c5b3a9db04d526a3ab00f4ba484d89af71430ed0543217ac7a0722427b47d735baf3ca4adc524
|
7
|
+
data.tar.gz: f854ebfc523130f308b289fcf575d782776057f4f2d212332d38eeb719444e0d8d7790d0f7e2824059a9bccea1f22be532e1bd55619ba2e66dd585930d4574f5
|
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,27 @@
|
|
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.3.0
|
7
|
+
|
8
|
+
- Have observing `ObservableArray` automatically notice if any values are nested arrays and observe them for micro-changes (recursively all the way down)
|
9
|
+
|
10
|
+
### 2.2.2
|
11
|
+
|
12
|
+
- Support auto-notifying on `ObservableArray#replace` observed changes
|
13
|
+
- Support auto-notifying on `ObservableArray#filter!` observed changes explicitly (even if `filter!` aliases `select!`)
|
14
|
+
- Support auto-notifying on `ObservableHash#filter!` observed changes explicitly (even if `filter!` aliases `select!`)
|
15
|
+
|
16
|
+
### 2.2.1
|
17
|
+
|
18
|
+
- 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!`
|
19
|
+
- Fix issue with `Glimmer::DataBinding::Observer.proc {|new_value| ...}.observe(array)` resulting in new_value coming across as `nil when the array is updated
|
20
|
+
|
21
|
+
### 2.2.0
|
22
|
+
|
23
|
+
- New `Glimmer::DataBinding::ObservableHash` support for observing `hash[key]=value` mutations
|
24
|
+
- Ensure observed ObservableHash values are enhanced with ObservableHash if they were of type Hash
|
25
|
+
- Ensure observed ObservableModel values are enhanced with ObservableHash if they were of type Hash
|
26
|
+
|
6
27
|
### 2.1.5
|
7
28
|
|
8
29
|
- Upgrade array_include_methods gem to version 1.4.0 (and fix minor version)
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 style="position: relative; top: 20px;" />](https://rubygems.org/gems/glimmer) Glimmer - DSL Framework for Ruby GUI and More
|
2
2
|
[![Gem Version](https://badge.fury.io/rb/glimmer.svg)](http://badge.fury.io/rb/glimmer)
|
3
3
|
[![rspec](https://github.com/AndyObtiva/glimmer/workflows/rspec/badge.svg)](https://github.com/AndyObtiva/glimmer/actions?query=workflow%3Arspec)
|
4
4
|
[![Coverage Status](https://coveralls.io/repos/github/AndyObtiva/glimmer/badge.svg?branch=master)](https://coveralls.io/github/AndyObtiva/glimmer?branch=master)
|
@@ -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.
|
213
|
+
- Add `gem 'glimmer', '~> 2.3.0'` to `Gemfile` and run `bundle` or run `gem install glimmer -v2.3.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
|
|
@@ -949,14 +949,6 @@ The main trade-off in using [Glimmer DSL for LibUI](https://rubygems.org/gems/gl
|
|
949
949
|
|
950
950
|
###### Hello, World!
|
951
951
|
|
952
|
-
Mac
|
953
|
-
|
954
|
-
![glimmer-dsl-libui-mac-basic-window.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-mac-basic-window.png)
|
955
|
-
|
956
|
-
Linux
|
957
|
-
|
958
|
-
![glimmer-dsl-libui-linux-basic-window.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-linux-basic-window.png)
|
959
|
-
|
960
952
|
```ruby
|
961
953
|
require 'glimmer-dsl-libui'
|
962
954
|
|
@@ -965,27 +957,25 @@ include Glimmer
|
|
965
957
|
window('hello world').show
|
966
958
|
```
|
967
959
|
|
968
|
-
###### Basic Button
|
969
|
-
|
970
960
|
Mac
|
971
961
|
|
972
|
-
![glimmer-dsl-libui-mac-basic-
|
973
|
-
![glimmer-dsl-libui-mac-basic-button-msg-box.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-mac-basic-button-msg-box.png)
|
962
|
+
![glimmer-dsl-libui-mac-basic-window.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-mac-basic-window.png)
|
974
963
|
|
975
964
|
Linux
|
976
965
|
|
977
|
-
![glimmer-dsl-libui-linux-basic-
|
978
|
-
|
966
|
+
![glimmer-dsl-libui-linux-basic-window.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-linux-basic-window.png)
|
967
|
+
|
968
|
+
###### Basic Button
|
979
969
|
|
980
970
|
```ruby
|
981
971
|
require 'glimmer-dsl-libui'
|
982
972
|
|
983
973
|
include Glimmer
|
984
974
|
|
985
|
-
window('hello world', 300, 200) {
|
975
|
+
window('hello world', 300, 200) {
|
986
976
|
button('Button') {
|
987
977
|
on_clicked do
|
988
|
-
msg_box(
|
978
|
+
msg_box('Information', 'You clicked the button')
|
989
979
|
end
|
990
980
|
}
|
991
981
|
|
@@ -995,223 +985,171 @@ window('hello world', 300, 200) { |w|
|
|
995
985
|
}.show
|
996
986
|
```
|
997
987
|
|
998
|
-
###### Control Gallery
|
999
|
-
|
1000
988
|
Mac
|
1001
989
|
|
1002
|
-
![glimmer-dsl-libui-mac-
|
990
|
+
![glimmer-dsl-libui-mac-basic-button.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-mac-basic-button.png)
|
991
|
+
![glimmer-dsl-libui-mac-basic-button-msg-box.png](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-mac-basic-button-msg-box.png)
|
1003
992
|
|
1004
993
|
Linux
|
1005
994
|
|
1006
|
-
![glimmer-dsl-libui-linux-
|
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
|
+
![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
|
+
|
998
|
+
###### Basic Table Progress Bar
|
1007
999
|
|
1008
1000
|
```ruby
|
1009
1001
|
require 'glimmer-dsl-libui'
|
1010
1002
|
|
1011
1003
|
include Glimmer
|
1012
1004
|
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1005
|
+
data = [
|
1006
|
+
['task 1', 0],
|
1007
|
+
['task 2', 15],
|
1008
|
+
['task 3', 100],
|
1009
|
+
['task 4', 75],
|
1010
|
+
['task 5', -1],
|
1011
|
+
]
|
1020
1012
|
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
}
|
1027
|
-
|
1028
|
-
quit_menu_item {
|
1029
|
-
on_clicked do
|
1030
|
-
puts 'Bye Bye'
|
1031
|
-
end
|
1032
|
-
}
|
1033
|
-
|
1034
|
-
preferences_menu_item # Can optionally contain an on_clicked listener
|
1035
|
-
}
|
1013
|
+
window('Task Progress', 300, 200) {
|
1014
|
+
vertical_box {
|
1015
|
+
table {
|
1016
|
+
text_column('Task')
|
1017
|
+
progress_bar_column('Progress')
|
1036
1018
|
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
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
|
1030
|
+
}
|
1042
1031
|
}
|
1043
|
-
}
|
1044
|
-
|
1045
|
-
menu('Help') {
|
1046
|
-
menu_item('Help')
|
1047
|
-
|
1048
|
-
about_menu_item # Can optionally contain an on_clicked listener
|
1049
|
-
}
|
1050
|
-
|
1051
|
-
MAIN_WINDOW = window('Control Gallery', 600, 500) {
|
1052
|
-
margined true
|
1053
|
-
|
1054
|
-
on_closing do
|
1055
|
-
puts 'Bye Bye'
|
1056
|
-
end
|
1057
|
-
|
1058
|
-
vertical_box {
|
1059
|
-
horizontal_box {
|
1060
|
-
group('Basic Controls') {
|
1061
|
-
vertical_box {
|
1062
|
-
button('Button') {
|
1063
|
-
stretchy false
|
1064
|
-
|
1065
|
-
on_clicked do
|
1066
|
-
msg_box(MAIN_WINDOW, 'Information', 'You clicked the button')
|
1067
|
-
end
|
1068
|
-
}
|
1032
|
+
}.show
|
1033
|
+
```
|
1069
1034
|
|
1070
|
-
|
1071
|
-
stretchy false
|
1035
|
+
Mac
|
1072
1036
|
|
1073
|
-
|
1074
|
-
checked = c.checked == 1
|
1075
|
-
MAIN_WINDOW.title = "Checkbox is #{checked}"
|
1076
|
-
c.text = "I am the checkbox (#{checked})"
|
1077
|
-
end
|
1078
|
-
}
|
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)
|
1079
1038
|
|
1080
|
-
|
1039
|
+
Linux
|
1081
1040
|
|
1082
|
-
|
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)
|
1083
1042
|
|
1084
|
-
|
1043
|
+
###### Area Gallery
|
1085
1044
|
|
1086
|
-
|
1045
|
+
```ruby
|
1046
|
+
require 'glimmer-dsl-libui'
|
1087
1047
|
|
1088
|
-
|
1048
|
+
include Glimmer
|
1089
1049
|
|
1090
|
-
|
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)
|
1091
1069
|
|
1092
|
-
|
1093
|
-
}
|
1070
|
+
closed true
|
1094
1071
|
}
|
1095
1072
|
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
on_changed do |s|
|
1106
|
-
puts "New Spinbox value: #{s.value}"
|
1107
|
-
end
|
1108
|
-
}
|
1109
|
-
|
1110
|
-
slider(0, 100) {
|
1111
|
-
stretchy false
|
1112
|
-
|
1113
|
-
on_changed do |s|
|
1114
|
-
v = s.value
|
1115
|
-
puts "New Slider value: #{v}"
|
1116
|
-
@progress_bar.value = v
|
1117
|
-
end
|
1118
|
-
}
|
1119
|
-
|
1120
|
-
@progress_bar = progress_bar { stretchy false }
|
1121
|
-
}
|
1122
|
-
}
|
1123
|
-
|
1124
|
-
group('Lists') {
|
1125
|
-
stretchy false
|
1126
|
-
|
1127
|
-
vertical_box {
|
1128
|
-
combobox {
|
1129
|
-
stretchy false
|
1130
|
-
items 'combobox Item 1', 'combobox Item 2', 'combobox Item 3' # also accepts a single array argument
|
1131
|
-
|
1132
|
-
on_selected do |c|
|
1133
|
-
puts "New combobox selection: #{c.selected}"
|
1134
|
-
end
|
1135
|
-
}
|
1136
|
-
|
1137
|
-
editable_combobox {
|
1138
|
-
stretchy false
|
1139
|
-
items 'Editable Item 1', 'Editable Item 2', 'Editable Item 3' # also accepts a single array argument
|
1140
|
-
}
|
1073
|
+
fill r: 202, g: 102, b: 104, a: 0.5
|
1074
|
+
stroke r: 0, g: 0, b: 0
|
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)
|
1141
1081
|
|
1142
|
-
|
1143
|
-
|
1144
|
-
}
|
1145
|
-
}
|
1146
|
-
}
|
1082
|
+
closed true
|
1083
|
+
}
|
1147
1084
|
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
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)
|
1153
1090
|
|
1154
|
-
|
1155
|
-
|
1156
|
-
end
|
1157
|
-
}
|
1158
|
-
}
|
1159
|
-
}
|
1160
|
-
|
1161
|
-
tab_item('Page 2') {
|
1162
|
-
horizontal_box
|
1163
|
-
}
|
1164
|
-
|
1165
|
-
tab_item('Page 3') {
|
1166
|
-
horizontal_box
|
1167
|
-
}
|
1168
|
-
}
|
1169
|
-
}
|
1091
|
+
fill r: 202, g: 102, b: 204, a: 0.5
|
1092
|
+
stroke r: 0, g: 0, b: 0, thickness: 2
|
1170
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
|
1171
1142
|
}
|
1172
|
-
}
|
1173
|
-
|
1174
|
-
MAIN_WINDOW.show
|
1143
|
+
}.show
|
1175
1144
|
```
|
1176
1145
|
|
1177
|
-
###### Basic Table
|
1178
|
-
|
1179
1146
|
Mac
|
1180
1147
|
|
1181
|
-
![glimmer-dsl-libui-mac-
|
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)
|
1182
1149
|
|
1183
1150
|
Linux
|
1184
1151
|
|
1185
|
-
![glimmer-dsl-libui-linux-
|
1186
|
-
|
1187
|
-
```ruby
|
1188
|
-
require 'glimmer-dsl-libui'
|
1189
|
-
|
1190
|
-
include Glimmer
|
1191
|
-
|
1192
|
-
data = [
|
1193
|
-
%w[cat meow],
|
1194
|
-
%w[dog woof],
|
1195
|
-
%w[chicken cock-a-doodle-doo],
|
1196
|
-
%w[hourse neigh],
|
1197
|
-
%w[cow moo]
|
1198
|
-
]
|
1199
|
-
|
1200
|
-
window('Animal sounds', 300, 200) {
|
1201
|
-
horizontal_box {
|
1202
|
-
table {
|
1203
|
-
text_column('Animal')
|
1204
|
-
text_column('Description')
|
1205
|
-
|
1206
|
-
cell_rows data
|
1207
|
-
}
|
1208
|
-
}
|
1209
|
-
|
1210
|
-
on_closing do
|
1211
|
-
puts 'Bye Bye'
|
1212
|
-
end
|
1213
|
-
}.show
|
1214
|
-
```
|
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
|
|
@@ -1222,12 +1160,13 @@ Glimmer enhances observed models automatically (including array operations like
|
|
1222
1160
|
This relies mainly on the Observer Design Pattern and the MVP (Model-View-Presenter) Architectural Pattern (a variation on MVC)
|
1223
1161
|
|
1224
1162
|
These are the main classes concerning data-binding:
|
1225
|
-
- `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
|
-
- `Observable`: General super-module for all observables. Main methods concerned are: `add_observer` and `remove_observer`
|
1227
|
-
- `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
|
-
- `ObservableArray`: Mixin module for any observable array collection that automatically handles notifying observers upon performing array mutation operations (e.g. `push` or `delete`)
|
1229
|
-
- `
|
1230
|
-
- `
|
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`)
|
1164
|
+
- `Glimmer::DataBinding::Observable`: General super-module for all observables. Main methods concerned are: `add_observer` and `remove_observer`
|
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.
|
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)
|
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!`)
|
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
|
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 `<=`
|
1231
1170
|
|
1232
1171
|
You may learn more from [Data-Binding](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md#data-binding) and [Observer](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md#observer) usage in [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt)
|
1233
1172
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.3.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.
|
5
|
+
# stub: glimmer 2.3.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "glimmer".freeze
|
9
|
-
s.version = "2.
|
9
|
+
s.version = "2.3.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-
|
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
|
14
|
+
s.date = "2021-10-07"
|
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",
|
@@ -32,6 +32,7 @@ Gem::Specification.new do |s|
|
|
32
32
|
"lib/glimmer/data_binding/model_binding.rb",
|
33
33
|
"lib/glimmer/data_binding/observable.rb",
|
34
34
|
"lib/glimmer/data_binding/observable_array.rb",
|
35
|
+
"lib/glimmer/data_binding/observable_hash.rb",
|
35
36
|
"lib/glimmer/data_binding/observable_model.rb",
|
36
37
|
"lib/glimmer/data_binding/observer.rb",
|
37
38
|
"lib/glimmer/data_binding/shine.rb",
|
@@ -50,7 +51,7 @@ Gem::Specification.new do |s|
|
|
50
51
|
]
|
51
52
|
s.homepage = "http://github.com/AndyObtiva/glimmer".freeze
|
52
53
|
s.licenses = ["MIT".freeze]
|
53
|
-
s.rubygems_version = "3.2.
|
54
|
+
s.rubygems_version = "3.2.28".freeze
|
54
55
|
s.summary = "Glimmer - DSL Engine for Ruby GUI and More".freeze
|
55
56
|
|
56
57
|
if s.respond_to? :specification_version then
|
@@ -198,7 +198,7 @@ module Glimmer
|
|
198
198
|
end
|
199
199
|
end
|
200
200
|
|
201
|
-
def call(value)
|
201
|
+
def call(value, *extra_args)
|
202
202
|
return if model.nil?
|
203
203
|
converted_value = value
|
204
204
|
invoke_property_writer(model, "#{property_name}=", converted_value) unless converted_value == evaluate_property
|
@@ -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,6 +30,20 @@ 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
48
|
def add_observer(observer, *element_properties)
|
34
49
|
element_properties = element_properties.flatten.compact.uniq
|
@@ -49,6 +64,23 @@ module Glimmer
|
|
49
64
|
element_properties_for(observer).each do |property|
|
50
65
|
observer.observe(element, property)
|
51
66
|
end
|
67
|
+
ensure_array_object_observer(element) if element.is_a?(Array)
|
68
|
+
end
|
69
|
+
|
70
|
+
def ensure_array_object_observer(object)
|
71
|
+
return unless object&.is_a?(Array)
|
72
|
+
array_object_observer = array_object_observer_for(object)
|
73
|
+
array_observer_registration = array_object_observer.observe(object)
|
74
|
+
property_observer_list.each do |observer|
|
75
|
+
my_registration = observer.registration_for(self)
|
76
|
+
observer.add_dependent(my_registration => array_observer_registration)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def array_object_observer_for(object)
|
81
|
+
@array_object_observers ||= Concurrent::Hash.new
|
82
|
+
@array_object_observers[object] = Notifier.new(self) unless @array_object_observers.has_key?(object)
|
83
|
+
@array_object_observers[object]
|
52
84
|
end
|
53
85
|
|
54
86
|
def remove_observer(observer, *element_properties)
|
@@ -76,6 +108,15 @@ module Glimmer
|
|
76
108
|
element_properties_for(observer).each do |property|
|
77
109
|
observer.unobserve(element, property)
|
78
110
|
end
|
111
|
+
if element.is_a?(ObservableArray)
|
112
|
+
array_object_observer_for(element).unobserve(element)
|
113
|
+
element.property_observer_list.select {|o| o.observable_array == self}.each do |o|
|
114
|
+
o.registrations.each do |registration|
|
115
|
+
registration.deregister
|
116
|
+
end
|
117
|
+
@array_object_observers.reject! {|k, v| v == o}
|
118
|
+
end
|
119
|
+
end
|
79
120
|
end
|
80
121
|
|
81
122
|
def has_observer?(observer)
|
@@ -99,7 +140,7 @@ module Glimmer
|
|
99
140
|
end
|
100
141
|
|
101
142
|
def notify_observers
|
102
|
-
property_observer_list.to_a.each(
|
143
|
+
property_observer_list.to_a.each { |o| o.call(self) }
|
103
144
|
end
|
104
145
|
|
105
146
|
def <<(element)
|
@@ -237,6 +278,21 @@ module Glimmer
|
|
237
278
|
end
|
238
279
|
end
|
239
280
|
|
281
|
+
def filter!(&block)
|
282
|
+
if block_given?
|
283
|
+
old_array = Array.new(self)
|
284
|
+
super(&block).tap do
|
285
|
+
(old_array - self).each do |old_value|
|
286
|
+
unregister_dependent_observers(old_value)
|
287
|
+
remove_element_observers(old_value)
|
288
|
+
end
|
289
|
+
notify_observers
|
290
|
+
end
|
291
|
+
else
|
292
|
+
super
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
240
296
|
def shuffle!(hash = nil)
|
241
297
|
(hash.nil? ? super() : super(random: hash[:random])).tap { notify_observers }
|
242
298
|
end
|
@@ -294,6 +350,17 @@ module Glimmer
|
|
294
350
|
end
|
295
351
|
end
|
296
352
|
|
353
|
+
def replace(other_array)
|
354
|
+
old_array = Array.new(self)
|
355
|
+
super(other_array).tap do
|
356
|
+
(old_array - self).each do |old_value|
|
357
|
+
unregister_dependent_observers(old_value)
|
358
|
+
remove_element_observers(old_value)
|
359
|
+
end
|
360
|
+
notify_observers
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
297
364
|
def unregister_dependent_observers(old_value)
|
298
365
|
return unless old_value.is_a?(ObservableModel) || old_value.is_a?(ObservableArray)
|
299
366
|
property_observer_list.each { |observer| observer.unregister_dependents_with_observable(observer.registration_for(self), old_value) }
|
@@ -0,0 +1,375 @@
|
|
1
|
+
# Copyright (c) 2007-2021 Andy Maleh
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
require 'glimmer/data_binding/observable'
|
23
|
+
require 'glimmer/data_binding/observer'
|
24
|
+
|
25
|
+
module Glimmer
|
26
|
+
module DataBinding
|
27
|
+
module ObservableHash
|
28
|
+
include Observable
|
29
|
+
|
30
|
+
class Notifier
|
31
|
+
include Observer
|
32
|
+
|
33
|
+
def initialize(observable_model, key)
|
34
|
+
@observable_model = observable_model
|
35
|
+
@key = key
|
36
|
+
end
|
37
|
+
|
38
|
+
def call(new_value=nil, *extra_args)
|
39
|
+
@observable_model.notify_observers(@key)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
OBSERVED_STORE_METHOD = lambda do |key, value|
|
44
|
+
if key_observer_list(key).empty?
|
45
|
+
if all_key_observer_list.empty?
|
46
|
+
self.send('__original__store', key, value)
|
47
|
+
else
|
48
|
+
old_value = self[key]
|
49
|
+
unregister_dependent_observers(nil, old_value) # remove dependent observers previously installed in ensure_array_object_observer and ensure_hash_object_observer
|
50
|
+
self.send('__original__store', key, value)
|
51
|
+
notify_observers(key)
|
52
|
+
ensure_array_object_observer(nil, value, old_value)
|
53
|
+
ensure_hash_object_observer(nil, value, old_value)
|
54
|
+
end
|
55
|
+
else
|
56
|
+
old_value = self[key]
|
57
|
+
unregister_dependent_observers(key, old_value) # remove dependent observers previously installed in ensure_array_object_observer and ensure_hash_object_observer
|
58
|
+
self.send('__original__store', key, value)
|
59
|
+
notify_observers(key)
|
60
|
+
ensure_array_object_observer(key, value, old_value)
|
61
|
+
ensure_hash_object_observer(key, value, old_value)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def add_observer(observer, key = nil)
|
66
|
+
return observer if has_observer?(observer, key)
|
67
|
+
key_observer_list(key) << observer
|
68
|
+
add_key_writer_observer(key)
|
69
|
+
observer
|
70
|
+
end
|
71
|
+
|
72
|
+
def remove_observer(observer, key = nil)
|
73
|
+
if has_observer?(observer, key)
|
74
|
+
key_observer_list(key).delete(observer)
|
75
|
+
observer.unobserve(self, key)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def remove_observers(key)
|
80
|
+
key_observer_hash[key].each do |observer|
|
81
|
+
remove_observer(observer, key)
|
82
|
+
end
|
83
|
+
key_observer_hash.delete(key)
|
84
|
+
end
|
85
|
+
|
86
|
+
def remove_all_observers
|
87
|
+
all_observers = key_observer_hash.clone
|
88
|
+
key_observer_hash.keys.each do |key|
|
89
|
+
remove_observers(key)
|
90
|
+
end
|
91
|
+
key_observer_hash.clear
|
92
|
+
all_observers
|
93
|
+
end
|
94
|
+
|
95
|
+
def has_observer?(observer, key = nil)
|
96
|
+
key_observer_list(key).include?(observer)
|
97
|
+
end
|
98
|
+
|
99
|
+
def has_observer_for_any_key?(observer)
|
100
|
+
key_observer_hash.values.map(&:to_a).reduce(:+).include?(observer)
|
101
|
+
end
|
102
|
+
|
103
|
+
def key_observer_hash
|
104
|
+
@key_observers ||= Hash.new
|
105
|
+
end
|
106
|
+
|
107
|
+
def key_observer_list(key)
|
108
|
+
key_observer_hash[key] = Concurrent::Set.new unless key_observer_hash[key]
|
109
|
+
key_observer_hash[key]
|
110
|
+
end
|
111
|
+
|
112
|
+
def all_key_observer_list
|
113
|
+
key_observer_list(nil)
|
114
|
+
end
|
115
|
+
|
116
|
+
def notify_observers(key)
|
117
|
+
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], key) }
|
119
|
+
end
|
120
|
+
|
121
|
+
def add_key_writer_observer(key = nil)
|
122
|
+
ensure_array_object_observer(key, self[key])
|
123
|
+
ensure_hash_object_observer(key, self[key])
|
124
|
+
begin
|
125
|
+
method('__original__store')
|
126
|
+
rescue
|
127
|
+
define_singleton_method('__original__store', store_method)
|
128
|
+
define_singleton_method('[]=', &OBSERVED_STORE_METHOD)
|
129
|
+
end
|
130
|
+
rescue => e
|
131
|
+
#ignore writing if no key writer exists
|
132
|
+
Glimmer::Config.logger.debug {"No need to observe store method: '[]='\n#{e.message}\n#{e.backtrace.join("\n")}"}
|
133
|
+
end
|
134
|
+
|
135
|
+
def store_method
|
136
|
+
self.class.instance_method('[]=') rescue self.method('[]=')
|
137
|
+
end
|
138
|
+
|
139
|
+
def unregister_dependent_observers(key, old_value)
|
140
|
+
# TODO look into optimizing this
|
141
|
+
return unless old_value.is_a?(ObservableModel) || old_value.is_a?(ObservableArray) || old_value.is_a?(ObservableHash)
|
142
|
+
key_observer_list(key).each { |observer| observer.unregister_dependents_with_observable(observer.registration_for(self, key), old_value) }
|
143
|
+
end
|
144
|
+
alias deregister_dependent_observers unregister_dependent_observers
|
145
|
+
|
146
|
+
def ensure_array_object_observer(key, object, old_object = nil)
|
147
|
+
return unless object&.is_a?(Array)
|
148
|
+
array_object_observer = array_object_observer_for(key)
|
149
|
+
array_observer_registration = array_object_observer.observe(object)
|
150
|
+
key_observer_list(key).each do |observer|
|
151
|
+
my_registration = observer.registration_for(self, key) # TODO eliminate repetition
|
152
|
+
observer.add_dependent(my_registration => array_observer_registration)
|
153
|
+
end
|
154
|
+
array_object_observer_for(key).unregister(old_object) if old_object.is_a?(ObservableArray)
|
155
|
+
end
|
156
|
+
|
157
|
+
def array_object_observer_for(key)
|
158
|
+
@array_object_observers ||= Concurrent::Hash.new
|
159
|
+
@array_object_observers[key] = ObservableModel::Notifier.new(self, key) unless @array_object_observers.has_key?(key)
|
160
|
+
@array_object_observers[key]
|
161
|
+
end
|
162
|
+
|
163
|
+
def ensure_hash_object_observer(key, object, old_object = nil)
|
164
|
+
return unless object&.is_a?(Hash)
|
165
|
+
hash_object_observer = hash_object_observer_for(key)
|
166
|
+
hash_observer_registration = hash_object_observer.observe(object)
|
167
|
+
key_observer_list(key).each do |observer|
|
168
|
+
my_registration = observer.registration_for(self, key) # TODO eliminate repetition
|
169
|
+
observer.add_dependent(my_registration => hash_observer_registration)
|
170
|
+
end
|
171
|
+
hash_object_observer_for(key).unregister(old_object) if old_object.is_a?(ObservableHash)
|
172
|
+
end
|
173
|
+
|
174
|
+
def hash_object_observer_for(key)
|
175
|
+
@hash_object_observers ||= Concurrent::Hash.new
|
176
|
+
@hash_object_observers[key] = ObservableModel::Notifier.new(self, key) unless @hash_object_observers.has_key?(key)
|
177
|
+
@hash_object_observers[key]
|
178
|
+
end
|
179
|
+
|
180
|
+
def delete(key, &block)
|
181
|
+
old_value = self[key]
|
182
|
+
unless old_value.nil?
|
183
|
+
unregister_dependent_observers(key, old_value)
|
184
|
+
unregister_dependent_observers(nil, old_value)
|
185
|
+
end
|
186
|
+
super(key, &block).tap do
|
187
|
+
notify_observers(key) unless old_value.nil?
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def delete_if(&block)
|
192
|
+
if block_given?
|
193
|
+
old_hash = self.dup
|
194
|
+
super(&block).tap do |new_hash|
|
195
|
+
deleted_keys = old_hash.keys - new_hash.keys
|
196
|
+
deleted_keys.each do |deleted_key|
|
197
|
+
deleted_value = old_hash[deleted_key]
|
198
|
+
unless deleted_value.nil?
|
199
|
+
unregister_dependent_observers(deleted_key, deleted_value)
|
200
|
+
unregister_dependent_observers(nil, deleted_value)
|
201
|
+
notify_observers(deleted_key)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
else
|
206
|
+
super
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def select!(&block)
|
211
|
+
if block_given?
|
212
|
+
old_hash = self.dup
|
213
|
+
super(&block).tap do |new_hash|
|
214
|
+
deleted_keys = old_hash.keys - new_hash.keys
|
215
|
+
deleted_keys.each do |deleted_key|
|
216
|
+
deleted_value = old_hash[deleted_key]
|
217
|
+
unless deleted_value.nil?
|
218
|
+
unregister_dependent_observers(deleted_key, deleted_value)
|
219
|
+
unregister_dependent_observers(nil, deleted_value)
|
220
|
+
notify_observers(deleted_key)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
else
|
225
|
+
super
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def filter!(&block)
|
230
|
+
if block_given?
|
231
|
+
old_hash = self.dup
|
232
|
+
super(&block).tap do |new_hash|
|
233
|
+
deleted_keys = old_hash.keys - new_hash.keys
|
234
|
+
deleted_keys.each do |deleted_key|
|
235
|
+
deleted_value = old_hash[deleted_key]
|
236
|
+
unless deleted_value.nil?
|
237
|
+
unregister_dependent_observers(deleted_key, deleted_value)
|
238
|
+
unregister_dependent_observers(nil, deleted_value)
|
239
|
+
notify_observers(deleted_key)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
else
|
244
|
+
super
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def keep_if(&block)
|
249
|
+
if block_given?
|
250
|
+
old_hash = self.dup
|
251
|
+
super(&block).tap do |new_hash|
|
252
|
+
deleted_keys = old_hash.keys - new_hash.keys
|
253
|
+
deleted_keys.each do |deleted_key|
|
254
|
+
deleted_value = old_hash[deleted_key]
|
255
|
+
unless deleted_value.nil?
|
256
|
+
unregister_dependent_observers(deleted_key, deleted_value)
|
257
|
+
unregister_dependent_observers(nil, deleted_value)
|
258
|
+
notify_observers(deleted_key)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
else
|
263
|
+
super
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def reject!(&block)
|
268
|
+
if block_given?
|
269
|
+
old_hash = self.dup
|
270
|
+
super(&block).tap do |new_hash|
|
271
|
+
deleted_keys = old_hash.keys - new_hash.keys
|
272
|
+
deleted_keys.each do |deleted_key|
|
273
|
+
deleted_value = old_hash[deleted_key]
|
274
|
+
unless deleted_value.nil?
|
275
|
+
unregister_dependent_observers(deleted_key, deleted_value)
|
276
|
+
unregister_dependent_observers(nil, deleted_value)
|
277
|
+
notify_observers(deleted_key)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
else
|
282
|
+
super
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def shift
|
287
|
+
old_hash = self.dup
|
288
|
+
super.tap do
|
289
|
+
new_hash = self
|
290
|
+
deleted_keys = old_hash.keys - new_hash.keys
|
291
|
+
deleted_keys.each do |deleted_key|
|
292
|
+
deleted_value = old_hash[deleted_key]
|
293
|
+
unless deleted_value.nil?
|
294
|
+
unregister_dependent_observers(deleted_key, deleted_value)
|
295
|
+
unregister_dependent_observers(nil, deleted_value)
|
296
|
+
notify_observers(deleted_key)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def merge!(*other_hashes, &block)
|
303
|
+
if other_hashes.empty?
|
304
|
+
super
|
305
|
+
else
|
306
|
+
old_hash = self.dup
|
307
|
+
super(*other_hashes, &block).tap do |new_hash|
|
308
|
+
changed_keys = other_hashes.map(&:keys).reduce(:+)
|
309
|
+
changed_keys.each do |changed_key|
|
310
|
+
old_value = old_hash[changed_key]
|
311
|
+
if new_hash[changed_key] != old_value
|
312
|
+
unregister_dependent_observers(changed_key, old_value)
|
313
|
+
unregister_dependent_observers(nil, old_value)
|
314
|
+
notify_observers(changed_key)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def replace(other_hash)
|
322
|
+
old_hash = self.dup
|
323
|
+
super(other_hash).tap do |new_hash|
|
324
|
+
changed_keys = old_hash.keys + new_hash.keys
|
325
|
+
changed_keys.each do |changed_key|
|
326
|
+
old_value = old_hash[changed_key]
|
327
|
+
if new_hash[changed_key] != old_value
|
328
|
+
unregister_dependent_observers(changed_key, old_value)
|
329
|
+
unregister_dependent_observers(nil, old_value)
|
330
|
+
notify_observers(changed_key)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def transform_keys!(hash2 = nil, &block)
|
337
|
+
if hash2.nil? && block.nil?
|
338
|
+
super
|
339
|
+
else
|
340
|
+
old_hash = self.dup
|
341
|
+
result = hash2.nil? ? super(&block) : super(hash2, &block)
|
342
|
+
result.tap do |new_hash|
|
343
|
+
changed_keys = old_hash.keys + new_hash.keys
|
344
|
+
changed_keys.each do |changed_key|
|
345
|
+
old_value = old_hash[changed_key]
|
346
|
+
if new_hash[changed_key] != old_value
|
347
|
+
unregister_dependent_observers(changed_key, old_value)
|
348
|
+
unregister_dependent_observers(nil, old_value)
|
349
|
+
notify_observers(changed_key)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
def transform_values!(&block)
|
357
|
+
if block_given?
|
358
|
+
old_hash = self.dup
|
359
|
+
super(&block).tap do |new_hash|
|
360
|
+
new_hash.keys.each do |changed_key|
|
361
|
+
old_value = old_hash[changed_key]
|
362
|
+
if new_hash[changed_key] != old_value
|
363
|
+
unregister_dependent_observers(changed_key, old_value)
|
364
|
+
unregister_dependent_observers(nil, old_value)
|
365
|
+
notify_observers(changed_key)
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
else
|
370
|
+
super
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
@@ -35,7 +35,7 @@ module Glimmer
|
|
35
35
|
@property_name = property_name
|
36
36
|
end
|
37
37
|
|
38
|
-
def call(new_value=nil)
|
38
|
+
def call(new_value=nil, *extra_args)
|
39
39
|
@observable_model.notify_observers(@property_name)
|
40
40
|
end
|
41
41
|
end
|
@@ -44,10 +44,11 @@ module Glimmer
|
|
44
44
|
property_writer_name = "#{property_name}="
|
45
45
|
lambda do |value|
|
46
46
|
old_value = self.send(property_name)
|
47
|
-
unregister_dependent_observers(property_name, old_value)
|
47
|
+
unregister_dependent_observers(property_name, old_value) # remove dependent observers previously installed in ensure_array_object_observer and ensure_hash_object_observer
|
48
48
|
self.send("__original__#{property_writer_name}", value)
|
49
49
|
notify_observers(property_name)
|
50
50
|
ensure_array_object_observer(property_name, value, old_value)
|
51
|
+
ensure_hash_object_observer(property_name, value, old_value)
|
51
52
|
end
|
52
53
|
end
|
53
54
|
|
@@ -106,6 +107,7 @@ module Glimmer
|
|
106
107
|
property_writer_name = "#{property_name}="
|
107
108
|
method(property_writer_name)
|
108
109
|
ensure_array_object_observer(property_name, send(property_name))
|
110
|
+
ensure_hash_object_observer(property_name, send(property_name))
|
109
111
|
begin
|
110
112
|
method("__original__#{property_writer_name}")
|
111
113
|
rescue
|
@@ -141,9 +143,26 @@ module Glimmer
|
|
141
143
|
|
142
144
|
def array_object_observer_for(property_name)
|
143
145
|
@array_object_observers ||= Concurrent::Hash.new
|
144
|
-
@array_object_observers[property_name] =
|
146
|
+
@array_object_observers[property_name] = Notifier.new(self, property_name) unless @array_object_observers.has_key?(property_name)
|
145
147
|
@array_object_observers[property_name]
|
146
148
|
end
|
149
|
+
|
150
|
+
def ensure_hash_object_observer(property_name, object, old_object = nil)
|
151
|
+
return unless object&.is_a?(Hash)
|
152
|
+
hash_object_observer = hash_object_observer_for(property_name)
|
153
|
+
hash_observer_registration = hash_object_observer.observe(object)
|
154
|
+
property_observer_list(property_name).each do |observer|
|
155
|
+
my_registration = observer.registration_for(self, property_name) # TODO eliminate repetition
|
156
|
+
observer.add_dependent(my_registration => hash_observer_registration)
|
157
|
+
end
|
158
|
+
hash_object_observer_for(property_name).unregister(old_object) if old_object.is_a?(ObservableHash)
|
159
|
+
end
|
160
|
+
|
161
|
+
def hash_object_observer_for(property_name)
|
162
|
+
@hash_object_observers ||= Concurrent::Hash.new
|
163
|
+
@hash_object_observers[property_name] = Notifier.new(self, property_name) unless @hash_object_observers.has_key?(property_name)
|
164
|
+
@hash_object_observers[property_name]
|
165
|
+
end
|
147
166
|
end
|
148
167
|
end
|
149
168
|
end
|
@@ -41,8 +41,8 @@ module Glimmer
|
|
41
41
|
end
|
42
42
|
|
43
43
|
# Called by observables once updates occur sending in the new_value if any
|
44
|
-
def call(new_value=nil)
|
45
|
-
@observer_block.call(new_value)
|
44
|
+
def call(new_value=nil, *extra_args)
|
45
|
+
@observer_block.call(new_value, *extra_args)
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
@@ -86,6 +86,8 @@ module Glimmer
|
|
86
86
|
# TODO refactor code to be more smart/polymorphic/automated and honor open/closed principle
|
87
87
|
if observable.is_a?(Array)
|
88
88
|
observable.extend(ObservableArray)
|
89
|
+
elsif observable.is_a?(Hash)
|
90
|
+
observable.extend(ObservableHash)
|
89
91
|
else
|
90
92
|
observable.extend(ObservableModel)
|
91
93
|
end
|
@@ -141,7 +143,7 @@ module Glimmer
|
|
141
143
|
dependents_for(registration).delete(dependent)
|
142
144
|
end
|
143
145
|
|
144
|
-
def call(new_value)
|
146
|
+
def call(new_value = nil, *extra_args)
|
145
147
|
raise Error, 'Not implemented!'
|
146
148
|
end
|
147
149
|
end
|
@@ -150,3 +152,4 @@ end
|
|
150
152
|
|
151
153
|
require 'glimmer/data_binding/observable_model'
|
152
154
|
require 'glimmer/data_binding/observable_array'
|
155
|
+
require 'glimmer/data_binding/observable_hash'
|
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.
|
4
|
+
version: 2.3.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-
|
11
|
+
date: 2021-10-07 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
|
207
|
-
|
208
|
-
Desktop Development GUI
|
209
|
-
|
210
|
-
DSL for
|
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: []
|
@@ -228,6 +229,7 @@ files:
|
|
228
229
|
- lib/glimmer/data_binding/model_binding.rb
|
229
230
|
- lib/glimmer/data_binding/observable.rb
|
230
231
|
- lib/glimmer/data_binding/observable_array.rb
|
232
|
+
- lib/glimmer/data_binding/observable_hash.rb
|
231
233
|
- lib/glimmer/data_binding/observable_model.rb
|
232
234
|
- lib/glimmer/data_binding/observer.rb
|
233
235
|
- lib/glimmer/data_binding/shine.rb
|
@@ -262,7 +264,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
262
264
|
- !ruby/object:Gem::Version
|
263
265
|
version: '0'
|
264
266
|
requirements: []
|
265
|
-
rubygems_version: 3.2.
|
267
|
+
rubygems_version: 3.2.28
|
266
268
|
signing_key:
|
267
269
|
specification_version: 4
|
268
270
|
summary: Glimmer - DSL Engine for Ruby GUI and More
|