glimmer-dsl-libui 0.4.10 → 0.4.11

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.4.10
1
+ # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.4.11
2
2
  ## Prerequisite-Free Ruby Desktop Development GUI Library
3
3
  [![Gem Version](https://badge.fury.io/rb/glimmer-dsl-libui.svg)](http://badge.fury.io/rb/glimmer-dsl-libui)
4
4
  [![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
@@ -77,6 +77,127 @@ Mac | Windows | Linux
77
77
  ----|---------|------
78
78
  ![glimmer-dsl-libui-mac-basic-table-progress-bar.png](images/glimmer-dsl-libui-mac-basic-table-progress-bar.png) | ![glimmer-dsl-libui-windows-basic-table-progress-bar.png](images/glimmer-dsl-libui-windows-basic-table-progress-bar.png) | ![glimmer-dsl-libui-linux-basic-table-progress-bar.png](images/glimmer-dsl-libui-linux-basic-table-progress-bar.png)
79
79
 
80
+ Form Table
81
+
82
+ ```ruby
83
+ require 'glimmer-dsl-libui'
84
+
85
+ class FormTable
86
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
87
+
88
+ include Glimmer
89
+
90
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
91
+
92
+ def initialize
93
+ @contacts = [
94
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
95
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
96
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
97
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
98
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
99
+ ]
100
+ end
101
+
102
+ def launch
103
+ window('Contacts', 600, 600) { |w|
104
+ margined true
105
+
106
+ vertical_box {
107
+ form {
108
+ stretchy false
109
+
110
+ entry {
111
+ label 'Name'
112
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
113
+ }
114
+
115
+ entry {
116
+ label 'Email'
117
+ text <=> [self, :email]
118
+ }
119
+
120
+ entry {
121
+ label 'Phone'
122
+ text <=> [self, :phone]
123
+ }
124
+
125
+ entry {
126
+ label 'City'
127
+ text <=> [self, :city]
128
+ }
129
+
130
+ entry {
131
+ label 'State'
132
+ text <=> [self, :state]
133
+ }
134
+ }
135
+
136
+ button('Save Contact') {
137
+ stretchy false
138
+
139
+ on_clicked do
140
+ new_row = [name, email, phone, city, state]
141
+ if new_row.include?('')
142
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
143
+ else
144
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to explicit data-binding
145
+ @unfiltered_contacts = @contacts.dup
146
+ self.name = '' # automatically clears name entry through explicit data-binding
147
+ self.email = ''
148
+ self.phone = ''
149
+ self.city = ''
150
+ self.state = ''
151
+ end
152
+ end
153
+ }
154
+
155
+ search_entry {
156
+ stretchy false
157
+ # bidirectional data-binding of text to self.filter_value with after_write option
158
+ text <=> [self, :filter_value,
159
+ after_write: ->(filter_value) { # execute after write to self.filter_value
160
+ @unfiltered_contacts ||= @contacts.dup
161
+ # Unfilter first to remove any previous filters
162
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
163
+ # Now, apply filter if entered
164
+ unless filter_value.empty?
165
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
166
+ contact.members.any? do |attribute|
167
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
168
+ end
169
+ end
170
+ end
171
+ }
172
+ ]
173
+ }
174
+
175
+ table {
176
+ text_column('Name')
177
+ text_column('Email')
178
+ text_column('Phone')
179
+ text_column('City')
180
+ text_column('State')
181
+
182
+ editable true
183
+ cell_rows <=> [self, :contacts] # explicit data-binding to Model Array
184
+
185
+ on_changed do |row, type, row_data|
186
+ puts "Row #{row} #{type}: #{row_data}"
187
+ end
188
+ }
189
+ }
190
+ }.show
191
+ end
192
+ end
193
+
194
+ FormTable.new.launch
195
+ ```
196
+
197
+ Mac | Windows | Linux
198
+ ----|---------|------
199
+ ![glimmer-dsl-libui-mac-form-table.png](images/glimmer-dsl-libui-mac-form-table.png) | ![glimmer-dsl-libui-windows-form-table.png](images/glimmer-dsl-libui-windows-form-table.png) | ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png)
200
+
80
201
  Area Gallery
81
202
 
82
203
  ```ruby
@@ -374,10 +495,20 @@ gem install glimmer-dsl-libui
374
495
  Or install via Bundler `Gemfile`:
375
496
 
376
497
  ```ruby
377
- gem 'glimmer-dsl-libui', '~> 0.4.10'
498
+ gem 'glimmer-dsl-libui', '~> 0.4.11'
499
+ ```
500
+
501
+ Test that installation worked by running the [Meta-Example](#examples):
502
+
503
+ ```
504
+ ruby -r glimmer-dsl-libui -e "require 'examples/meta_example'"
378
505
  ```
379
506
 
380
- Add `require 'glimmer-dsl-libui'` at the top, and then `include Glimmer` into the top-level main object for testing or into an actual class for serious usage.
507
+ Mac | Windows | Linux
508
+ ----|---------|------
509
+ ![glimmer-dsl-libui-mac-meta-example.png](images/glimmer-dsl-libui-mac-meta-example.png) | ![glimmer-dsl-libui-windows-meta-example.png](images/glimmer-dsl-libui-windows-meta-example.png) | ![glimmer-dsl-libui-linux-meta-example.png](images/glimmer-dsl-libui-linux-meta-example.png)
510
+
511
+ Now to use [glimmer-dsl-libui](https://rubygems.org/gems/glimmer-dsl-libui), add `require 'glimmer-dsl-libui'` at the top, and then `include Glimmer` into the top-level main object for testing or into an actual class for serious usage.
381
512
 
382
513
  Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
383
514
 
@@ -452,7 +583,7 @@ Keyword(Args) | Properties | Listeners
452
583
  `about_menu_item` | None | `on_clicked`
453
584
  `area` | `auto_draw_enabled` | `on_draw(area_draw_params)`, `on_mouse_event(area_mouse_event)`, `on_mouse_down(area_mouse_event)`, `on_mouse_up(area_mouse_event)`, `on_mouse_drag_started(area_mouse_event)`, `on_mouse_dragged(area_mouse_event)`, `on_mouse_dropped(area_mouse_event)`, `on_mouse_entered`, `on_mouse_exited`, `on_key_event(area_key_event)`, `on_key_down(area_key_event)`, `on_key_up(area_key_event)`
454
585
  `arc(x_center as Numeric, y_center as Numeric, radius as Numeric, start_angle as Numeric, sweep as Numeric, is_negative as Boolean)` | `x_center` (`Numeric`), `y_center` (`Numeric`), `radius` (`Numeric`), `start_angle` (`Numeric`), `sweep` (`Numeric`), `is_negative` (Boolean) | None
455
- `background_color_column(name as String)` | None | None
586
+ `background_color_column` | None | None
456
587
  `bezier(c1_x as Numeric, c1_y as Numeric, c2_x as Numeric, c2_y as Numeric, end_x as Numeric, end_y as Numeric)` | `c1_x` (`Numeric`), `c1_y` (`Numeric`), `c2_x` (`Numeric`), `c2_y` (`Numeric`), `end_x` (`Numeric`), `end_y` (`Numeric`) | None
457
588
  `button(text as String)` | `text` (`String`) | `on_clicked`
458
589
  `button_column(name as String)` | `enabled` (Boolean) | None
@@ -616,159 +747,147 @@ Note that the `cell_rows` property declaration results in "implicit data-binding
616
747
  - Inserting cell rows: Calling `Array#<<`, `Array#push`, `Array#prepend`, or any insertion/addition `Array` method automatically inserts rows in actual `table` control
617
748
  - Changing cell rows: Calling `Array#[]=`, `Array#map!`, or any update `Array` method automatically updates rows in actual `table` control
618
749
 
619
- Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
620
-
621
- ```ruby
622
- require 'glimmer-dsl-libui'
623
-
624
- class FormTable
625
- include Glimmer
626
-
627
- attr_accessor :name, :email, :phone, :city, :state, :filter_value
628
-
629
- def initialize
630
- @data = [
631
- ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],
632
- ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],
633
- ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],
634
- ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],
635
- ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
636
- ]
637
- end
638
-
639
- def launch
640
- window('Contacts', 600, 600) { |w|
641
- margined true
642
-
643
- vertical_box {
644
- form {
645
- stretchy false
646
-
647
- entry {
648
- label 'Name'
649
- text <=> [self, :name]
650
- }
651
-
652
- entry {
653
- label 'Email'
654
- text <=> [self, :email]
655
- }
656
-
657
- entry {
658
- label 'Phone'
659
- text <=> [self, :phone]
660
- }
661
-
662
- entry {
663
- label 'City'
664
- text <=> [self, :city]
665
- }
666
-
667
- entry {
668
- label 'State'
669
- text <=> [self, :state]
670
- }
671
- }
672
-
673
- button('Save Contact') {
674
- stretchy false
675
-
676
- on_clicked do
677
- new_row = [name, email, phone, city, state]
678
- if new_row.include?('')
679
- msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
680
- else
681
- @data << new_row # automatically inserts a row into the table due to implicit data-binding
682
- @unfiltered_data = @data.dup
683
- self.name = '' # automatically clears name entry through explicit data-binding
684
- self.email = ''
685
- self.phone = ''
686
- self.city = ''
687
- self.state = ''
688
- end
689
- end
690
- }
691
-
692
- search_entry {
693
- stretchy false
694
- text <=> [self, :filter_value, # bidirectional data-binding of text to self.filter_value with after_write option
695
- after_write: ->(filter_value) { # execute after write to self.filter_value
696
- @unfiltered_data ||= @data.dup
697
- # Unfilter first to remove any previous filters
698
- @data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
699
- # Now, apply filter if entered
700
- unless filter_value.empty?
701
- @data.filter! do |row_data| # affects table indirectly through implicit data-binding
702
- row_data.any? do |cell|
703
- cell.to_s.downcase.include?(filter_value.downcase)
704
- end
705
- end
706
- end
707
- }
708
- ]
709
- }
710
-
711
- table {
712
- text_column('Name')
713
- text_column('Email')
714
- text_column('Phone')
715
- text_column('City')
716
- text_column('State')
717
-
718
- cell_rows @data # implicit data-binding
719
-
720
- on_changed do |row, type, row_data|
721
- puts "Row #{row} #{type}: #{row_data}"
722
- end
723
- }
724
- }
725
- }.show
726
- end
727
- end
728
-
729
- FormTable.new.launch
730
- ```
731
-
732
- ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png)
733
-
734
- Learn more by checking out [examples](#examples).
735
-
736
- ### Area API
737
-
738
- The `area` control is a canvas-like control for drawing paths that can be used in one of two ways:
739
- - Declaratively via stable paths: useful for stable paths that will not change often later on. Simply nest `path` and figures like `rectangle` and all drawing logic is generated automatically. Path proxy objects are preserved across redraws assuming there would be relatively few stable paths (mostly for decorative reasons).
740
- - Semi-declaratively via on_draw listener dynamic paths: useful for more dynamic paths that will definitely change very often. Open an `on_draw` listener block that receives an [`area_draw_params`](#area-draw-params) argument and nest `path` and figures like `rectangle` and all drawing logic is generated automatically. Path proxy objects are destroyed (thrown-away) at the end of drawing, thus having less memory overhead for drawing thousands of dynamic paths.
741
-
742
- Note that when nesting an `area` directly underneath `window` (without a layout control like `vertical_box`), it is automatically reparented with `vertical_box` in between the `window` and `area` since it would not show up on Linux otherwise.
750
+ ([explicit data-binding](#data-binding) supports everything available with implicit data-binding too)
743
751
 
744
- Here is an example of a declarative `area` with a stable path (you may copy/paste in [`girb`](#girb-glimmer-irb)):
752
+ Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
745
753
 
746
754
  ```ruby
747
755
  require 'glimmer-dsl-libui'
748
756
 
749
757
  include Glimmer
750
758
 
751
- window('Basic Area', 400, 400) {
759
+ data = [
760
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],
761
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],
762
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],
763
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],
764
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
765
+ ]
766
+
767
+ window('Contacts', 600, 600) { |w|
752
768
  margined true
753
769
 
754
770
  vertical_box {
755
- area {
756
- path { # a stable path is added declaratively
757
- rectangle(0, 0, 400, 400)
758
-
759
- fill r: 102, g: 102, b: 204, a: 1.0
771
+ form {
772
+ stretchy false
773
+
774
+ @name_entry = entry {
775
+ label 'Name'
760
776
  }
761
- }
762
- }
763
- }.show
764
- ```
765
-
766
- Mac | Windows | Linux
767
- ----|---------|------
768
- ![glimmer-dsl-libui-mac-basic-area.png](images/glimmer-dsl-libui-mac-basic-area.png) | ![glimmer-dsl-libui-windows-basic-area.png](images/glimmer-dsl-libui-windows-basic-area.png) | ![glimmer-dsl-libui-linux-basic-area.png](images/glimmer-dsl-libui-linux-basic-area.png)
769
-
770
- Here is the same example using a semi-declarative `area` with `on_draw` listener that receives a [`area_draw_params`](#area-draw-params) argument and a dynamic path (you may copy/paste in [`girb`](#girb-glimmer-irb)):
771
-
777
+
778
+ @email_entry = entry {
779
+ label 'Email'
780
+ }
781
+
782
+ @phone_entry = entry {
783
+ label 'Phone'
784
+ }
785
+
786
+ @city_entry = entry {
787
+ label 'City'
788
+ }
789
+
790
+ @state_entry = entry {
791
+ label 'State'
792
+ }
793
+ }
794
+
795
+ button('Save Contact') {
796
+ stretchy false
797
+
798
+ on_clicked do
799
+ new_row = [@name_entry.text, @email_entry.text, @phone_entry.text, @city_entry.text, @state_entry.text]
800
+ if new_row.include?('')
801
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
802
+ else
803
+ data << new_row # automatically inserts a row into the table due to implicit data-binding
804
+ @unfiltered_data = data.dup
805
+ @name_entry.text = ''
806
+ @email_entry.text = ''
807
+ @phone_entry.text = ''
808
+ @city_entry.text = ''
809
+ @state_entry.text = ''
810
+ end
811
+ end
812
+ }
813
+
814
+ search_entry { |se|
815
+ stretchy false
816
+
817
+ on_changed do
818
+ filter_value = se.text
819
+ @unfiltered_data ||= data.dup
820
+ # Unfilter first to remove any previous filters
821
+ data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
822
+ # Now, apply filter if entered
823
+ unless filter_value.empty?
824
+ data.filter! do |row_data| # affects table indirectly through implicit data-binding
825
+ row_data.any? do |cell|
826
+ cell.to_s.downcase.include?(filter_value.downcase)
827
+ end
828
+ end
829
+ end
830
+ end
831
+ }
832
+
833
+ table {
834
+ text_column('Name')
835
+ text_column('Email')
836
+ text_column('Phone')
837
+ text_column('City')
838
+ text_column('State')
839
+
840
+ editable true
841
+ cell_rows data # implicit data-binding to raw data Array of Arrays
842
+
843
+ on_changed do |row, type, row_data|
844
+ puts "Row #{row} #{type}: #{row_data}"
845
+ end
846
+ }
847
+ }
848
+ }.show
849
+ ```
850
+
851
+ ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png)
852
+
853
+ Learn more by checking out [examples](#examples).
854
+
855
+ ### Area API
856
+
857
+ The `area` control is a canvas-like control for drawing paths that can be used in one of two ways:
858
+ - Declaratively via stable paths: useful for stable paths that will not change often later on. Simply nest `path` and figures like `rectangle` and all drawing logic is generated automatically. Path proxy objects are preserved across redraws assuming there would be relatively few stable paths (mostly for decorative reasons).
859
+ - Semi-declaratively via on_draw listener dynamic paths: useful for more dynamic paths that will definitely change very often. Open an `on_draw` listener block that receives an [`area_draw_params`](#area-draw-params) argument and nest `path` and figures like `rectangle` and all drawing logic is generated automatically. Path proxy objects are destroyed (thrown-away) at the end of drawing, thus having less memory overhead for drawing thousands of dynamic paths.
860
+
861
+ Note that when nesting an `area` directly underneath `window` (without a layout control like `vertical_box`), it is automatically reparented with `vertical_box` in between the `window` and `area` since it would not show up on Linux otherwise.
862
+
863
+ Here is an example of a declarative `area` with a stable path (you may copy/paste in [`girb`](#girb-glimmer-irb)):
864
+
865
+ ```ruby
866
+ require 'glimmer-dsl-libui'
867
+
868
+ include Glimmer
869
+
870
+ window('Basic Area', 400, 400) {
871
+ margined true
872
+
873
+ vertical_box {
874
+ area {
875
+ path { # a stable path is added declaratively
876
+ rectangle(0, 0, 400, 400)
877
+
878
+ fill r: 102, g: 102, b: 204, a: 1.0
879
+ }
880
+ }
881
+ }
882
+ }.show
883
+ ```
884
+
885
+ Mac | Windows | Linux
886
+ ----|---------|------
887
+ ![glimmer-dsl-libui-mac-basic-area.png](images/glimmer-dsl-libui-mac-basic-area.png) | ![glimmer-dsl-libui-windows-basic-area.png](images/glimmer-dsl-libui-windows-basic-area.png) | ![glimmer-dsl-libui-linux-basic-area.png](images/glimmer-dsl-libui-linux-basic-area.png)
888
+
889
+ Here is the same example using a semi-declarative `area` with `on_draw` listener that receives a [`area_draw_params`](#area-draw-params) argument and a dynamic path (you may copy/paste in [`girb`](#girb-glimmer-irb)):
890
+
772
891
  ```ruby
773
892
  require 'glimmer-dsl-libui'
774
893
 
@@ -799,9 +918,9 @@ Check [examples/dynamic_area.rb](#dynamic-area) for a more detailed semi-declara
799
918
  - `scroll_to(x as Numeric, y as Numeric, width as Numeric = main_window.width, height as Numeric = main_window.height)`: scrolls to `x`/`y` location with `width` and `height` viewport size.
800
919
  - `set_size(width as Numeric, height as Numeric)`: set size of scrolling area, which must must exceed that of visible viewport in order for scrolling to be enabled.
801
920
 
802
- Mac |Linux
803
- ----|-----
804
- ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png) | ![glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-basic-scrolling-area.png) ![glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-basic-scrolling-area-scrolled.png)
921
+ Mac | Windows | Linux
922
+ ----|---------|------
923
+ ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png) | ![glimmer-dsl-libui-windows-dynamic-area.png](images/glimmer-dsl-libui-windows-basic-scrolling-area.png) ![glimmer-dsl-libui-windows-dynamic-area-updated.png](images/glimmer-dsl-libui-windows-basic-scrolling-area-scrolled.png) | ![glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-basic-scrolling-area.png) ![glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-basic-scrolling-area-scrolled.png)
805
924
 
806
925
  Check [examples/basic_scrolling_area.rb](#basic-scrolling-area) for a more detailed example.
807
926
 
@@ -1217,6 +1336,7 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t
1217
1336
  - When destroying a control nested under a `horizontal_box` or `vertical_box`, it is automatically deleted from the box's children
1218
1337
  - When destroying a control nested under a `form`, it is automatically deleted from the form's children
1219
1338
  - When destroying a control nested under a `window` or `group`, it is automatically unset as their child to allow successful destruction
1339
+ - When destroying a control that has a data-binding to a model attribute, the data-binding observer registration is automatically deregistered
1220
1340
  - For `date_time_picker`, `date_picker`, and `time_picker`, make sure `time` hash values for `mon`, `wday`, and `yday` are 1-based instead of [libui](https://github.com/andlabs/libui) original 0-based values, and return `dst` as Boolean instead of `isdst` as `1`/`0`
1221
1341
  - Smart defaults for `grid` child properties are `left` (`0`), `top` (`0`), `xspan` (`1`), `yspan` (`1`), `hexpand` (`false`), `halign` (`:fill`), `vexpand` (`false`), and `valign` (`:fill`)
1222
1342
  - The `table` control automatically constructs required `TableModelHandler`, `TableModel`, and `TableParams`, calculating all their arguments from `cell_rows` and `editable` properties (e.g. `NumRows`) as well as nested columns (e.g. `text_column`)
@@ -1491,6 +1611,7 @@ Learn more from data-binding usage in [Login](#login) (4 data-binding versions),
1491
1611
  - `table` `progress_bar` column on Windows cannot be updated with a positive value if it started initially with `-1` (it ignores update to avoid crashing due to an issue in [libui](https://github.com/andlabs/libui) on Windows.
1492
1612
  - It seems that [libui](https://github.com/andlabs/libui) does not support nesting multiple `area` controls under a `grid` as only the first one shows up in that scenario. To workaround that limitation, use a `vertical_box` with nested `horizontal_box`s instead to include multiple `area`s in a GUI.
1493
1613
  - As per the code of [examples/basic_transform.rb](#basic-transform), Windows requires different ordering of transforms than Mac and Linux.
1614
+ - `scrolling_area#scroll_to` does not seem to work on Windows and Linux, but works fine on Mac
1494
1615
 
1495
1616
  ### Original API
1496
1617
 
@@ -3012,22 +3133,28 @@ Mac | Windows | Linux
3012
3133
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
3013
3134
 
3014
3135
  ```ruby
3015
- # frozen_string_literal: true
3016
-
3017
3136
  require 'glimmer-dsl-libui'
3018
3137
 
3019
3138
  class BasicTableButton
3139
+ BasicAnimal = Struct.new(:name, :sound)
3140
+
3141
+ class Animal < BasicAnimal
3142
+ def action
3143
+ 'delete'
3144
+ end
3145
+ end
3146
+
3020
3147
  include Glimmer
3021
3148
 
3022
- attr_accessor :data
3149
+ attr_accessor :animals
3023
3150
 
3024
3151
  def initialize
3025
- @data = [
3026
- %w[cat meow delete],
3027
- %w[dog woof delete],
3028
- %w[chicken cock-a-doodle-doo delete],
3029
- %w[horse neigh delete],
3030
- %w[cow moo delete]
3152
+ @animals = [
3153
+ Animal.new('cat', 'meow'),
3154
+ Animal.new('dog', 'woof'),
3155
+ Animal.new('chicken', 'cock-a-doodle-doo'),
3156
+ Animal.new('horse', 'neigh'),
3157
+ Animal.new('cow', 'moo'),
3031
3158
  ]
3032
3159
  end
3033
3160
 
@@ -3040,17 +3167,19 @@ class BasicTableButton
3040
3167
  button_column('Action') {
3041
3168
  on_clicked do |row|
3042
3169
  # Option 1: direct data deletion is the simpler solution
3043
- # @data.delete_at(row) # automatically deletes actual table row due to explicit data-binding
3170
+ # @animals.delete_at(row) # automatically deletes actual table row due to explicit data-binding
3044
3171
 
3045
- # Option 2: cloning only to demonstrate table row deletion upon explicit setting of data attribute (cloning is not recommended beyond demonstrating this point)
3046
- new_data = @data.clone
3047
- new_data.delete_at(row)
3048
- self.data = new_data # automatically loses deleted table row due to explicit data-binding
3172
+ # Option 2: cloning only to demonstrate table row deletion upon explicit setting of animals attribute (cloning is not recommended beyond demonstrating this point)
3173
+ new_animals = @animals.clone
3174
+ new_animals.delete_at(row)
3175
+ self.animals = new_animals # automatically loses deleted table row due to explicit data-binding
3049
3176
  end
3050
3177
  }
3051
3178
 
3052
- cell_rows <=> [self, :data] # explicit data-binding of table cell_rows to self.data
3053
3179
 
3180
+ cell_rows <= [self, :animals, column_attributes: {'Animal' => :name, 'Description' => :sound}]
3181
+
3182
+ # explicit unidirectional data-binding of table cell_rows to self.animals
3054
3183
  on_changed do |row, type, row_data|
3055
3184
  puts "Row #{row} #{type}: #{row_data}"
3056
3185
  $stdout.flush
@@ -3299,7 +3428,7 @@ window('Animals', 500, 200) {
3299
3428
  text_color_column('Sound')
3300
3429
  checkbox_text_color_column('Description')
3301
3430
  image_text_color_column('GUI')
3302
- background_color_column('Mammal')
3431
+ background_color_column # must be the last column
3303
3432
 
3304
3433
  cell_rows data
3305
3434
  }
@@ -3341,7 +3470,7 @@ window('Animals', 500, 200) {
3341
3470
  text_color_column('Sound')
3342
3471
  checkbox_text_color_column('Description')
3343
3472
  image_text_color_column('GUI')
3344
- background_color_column('Mammal')
3473
+ background_color_column
3345
3474
 
3346
3475
  cell_rows data
3347
3476
  }
@@ -3483,9 +3612,9 @@ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/
3483
3612
  ruby -r glimmer-dsl-libui -e "require 'examples/basic_scrolling_area'"
3484
3613
  ```
3485
3614
 
3486
- Mac | Linux
3487
- ----|------
3488
- ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png) | ![glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-basic-scrolling-area.png) ![glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-basic-scrolling-area-scrolled.png)
3615
+ Mac | Windows | Linux
3616
+ ----|---------|------
3617
+ ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png) | ![glimmer-dsl-libui-windows-dynamic-area.png](images/glimmer-dsl-libui-windows-basic-scrolling-area.png) ![glimmer-dsl-libui-windows-dynamic-area-updated.png](images/glimmer-dsl-libui-windows-basic-scrolling-area-scrolled.png) | ![glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-basic-scrolling-area.png) ![glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-basic-scrolling-area-scrolled.png)
3489
3618
 
3490
3619
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3491
3620
 
@@ -3572,6 +3701,8 @@ BasicScrollingArea.new.launch
3572
3701
 
3573
3702
  #### Basic Image
3574
3703
 
3704
+ Please note the caveats of [Area Image](#area-image) **(Alpha Feature)** with regards to this example.
3705
+
3575
3706
  [examples/basic_image.rb](examples/basic_image.rb)
3576
3707
 
3577
3708
  Run with this command from the root of the project if you cloned the project:
@@ -4822,9 +4953,9 @@ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/
4822
4953
  ruby -r glimmer-dsl-libui -e "require 'examples/button_counter'"
4823
4954
  ```
4824
4955
 
4825
- Mac | Linux
4826
- ----|------
4827
- ![glimmer-dsl-libui-mac-button-counter.png](images/glimmer-dsl-libui-mac-button-counter.png) | ![glimmer-dsl-libui-linux-button-counter.png](images/glimmer-dsl-libui-linux-button-counter.png)
4956
+ Mac | Windows | Linux
4957
+ ----|---------|------
4958
+ ![glimmer-dsl-libui-mac-button-counter.png](images/glimmer-dsl-libui-mac-button-counter.png) | ![glimmer-dsl-libui-windows-button-counter.png](images/glimmer-dsl-libui-windows-button-counter.png) | ![glimmer-dsl-libui-linux-button-counter.png](images/glimmer-dsl-libui-linux-button-counter.png)
4828
4959
 
4829
4960
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
4830
4961
 
@@ -6263,8 +6394,8 @@ window('Editable animal sounds', 300, 200) {
6263
6394
  text_column('Animal')
6264
6395
  text_column('Description')
6265
6396
 
6266
- cell_rows data
6267
6397
  editable true
6398
+ cell_rows data
6268
6399
 
6269
6400
  on_changed do |row, type, row_data| # fires on all changes (even ones happening through data array)
6270
6401
  puts "Row #{row} #{type}: #{row_data}"
@@ -6302,7 +6433,359 @@ Mac | Windows | Linux
6302
6433
  ----|---------|------
6303
6434
  ![glimmer-dsl-libui-mac-form-table.png](images/glimmer-dsl-libui-mac-form-table.png) ![glimmer-dsl-libui-mac-form-table-contact-entered.png](images/glimmer-dsl-libui-mac-form-table-contact-entered.png) ![glimmer-dsl-libui-mac-form-table-filtered.png](images/glimmer-dsl-libui-mac-form-table-filtered.png) | ![glimmer-dsl-libui-windows-form-table.png](images/glimmer-dsl-libui-windows-form-table.png) ![glimmer-dsl-libui-windows-form-table-contact-entered.png](images/glimmer-dsl-libui-windows-form-table-contact-entered.png) ![glimmer-dsl-libui-windows-form-table-filtered.png](images/glimmer-dsl-libui-windows-form-table-filtered.png) | ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png) ![glimmer-dsl-libui-linux-form-table-contact-entered.png](images/glimmer-dsl-libui-linux-form-table-contact-entered.png) ![glimmer-dsl-libui-linux-form-table-filtered.png](images/glimmer-dsl-libui-linux-form-table-filtered.png)
6304
6435
 
6305
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
6436
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
6437
+
6438
+ ```ruby
6439
+ require 'glimmer-dsl-libui'
6440
+
6441
+ class FormTable
6442
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
6443
+
6444
+ include Glimmer
6445
+
6446
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
6447
+
6448
+ def initialize
6449
+ @contacts = [
6450
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
6451
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
6452
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
6453
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
6454
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
6455
+ ]
6456
+ end
6457
+
6458
+ def launch
6459
+ window('Contacts', 600, 600) { |w|
6460
+ margined true
6461
+
6462
+ vertical_box {
6463
+ form {
6464
+ stretchy false
6465
+
6466
+ entry {
6467
+ label 'Name'
6468
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6469
+ }
6470
+
6471
+ entry {
6472
+ label 'Email'
6473
+ text <=> [self, :email]
6474
+ }
6475
+
6476
+ entry {
6477
+ label 'Phone'
6478
+ text <=> [self, :phone]
6479
+ }
6480
+
6481
+ entry {
6482
+ label 'City'
6483
+ text <=> [self, :city]
6484
+ }
6485
+
6486
+ entry {
6487
+ label 'State'
6488
+ text <=> [self, :state]
6489
+ }
6490
+ }
6491
+
6492
+ button('Save Contact') {
6493
+ stretchy false
6494
+
6495
+ on_clicked do
6496
+ new_row = [name, email, phone, city, state]
6497
+ if new_row.include?('')
6498
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6499
+ else
6500
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to explicit data-binding
6501
+ @unfiltered_contacts = @contacts.dup
6502
+ self.name = '' # automatically clears name entry through explicit data-binding
6503
+ self.email = ''
6504
+ self.phone = ''
6505
+ self.city = ''
6506
+ self.state = ''
6507
+ end
6508
+ end
6509
+ }
6510
+
6511
+ search_entry {
6512
+ stretchy false
6513
+ # bidirectional data-binding of text to self.filter_value with after_write option
6514
+ text <=> [self, :filter_value,
6515
+ after_write: ->(filter_value) { # execute after write to self.filter_value
6516
+ @unfiltered_contacts ||= @contacts.dup
6517
+ # Unfilter first to remove any previous filters
6518
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
6519
+ # Now, apply filter if entered
6520
+ unless filter_value.empty?
6521
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
6522
+ contact.members.any? do |attribute|
6523
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
6524
+ end
6525
+ end
6526
+ end
6527
+ }
6528
+ ]
6529
+ }
6530
+
6531
+ table {
6532
+ text_column('Name')
6533
+ text_column('Email')
6534
+ text_column('Phone')
6535
+ text_column('City')
6536
+ text_column('State')
6537
+
6538
+ editable true
6539
+ cell_rows <=> [self, :contacts] # explicit data-binding to Model Array
6540
+
6541
+ on_changed do |row, type, row_data|
6542
+ puts "Row #{row} #{type}: #{row_data}"
6543
+ end
6544
+ }
6545
+ }
6546
+ }.show
6547
+ end
6548
+ end
6549
+
6550
+ FormTable.new.launch
6551
+ ```
6552
+
6553
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
6554
+
6555
+ ```ruby
6556
+ require 'glimmer-dsl-libui'
6557
+
6558
+ class FormTable
6559
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
6560
+
6561
+ include Glimmer
6562
+
6563
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
6564
+
6565
+ def initialize
6566
+ @contacts = [
6567
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
6568
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
6569
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
6570
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
6571
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
6572
+ ]
6573
+ end
6574
+
6575
+ def launch
6576
+ window('Contacts', 600, 600) { |w|
6577
+ margined true
6578
+
6579
+ vertical_box {
6580
+ form {
6581
+ stretchy false
6582
+
6583
+ entry {
6584
+ label 'Name'
6585
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6586
+ }
6587
+
6588
+ entry {
6589
+ label 'Email'
6590
+ text <=> [self, :email]
6591
+ }
6592
+
6593
+ entry {
6594
+ label 'Phone'
6595
+ text <=> [self, :phone]
6596
+ }
6597
+
6598
+ entry {
6599
+ label 'City'
6600
+ text <=> [self, :city]
6601
+ }
6602
+
6603
+ entry {
6604
+ label 'State'
6605
+ text <=> [self, :state]
6606
+ }
6607
+ }
6608
+
6609
+ button('Save Contact') {
6610
+ stretchy false
6611
+
6612
+ on_clicked do
6613
+ new_row = [name, email, phone, city, state]
6614
+ if new_row.include?('')
6615
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6616
+ else
6617
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to implicit data-binding
6618
+ @unfiltered_contacts = @contacts.dup
6619
+ self.name = '' # automatically clears name entry through explicit data-binding
6620
+ self.email = ''
6621
+ self.phone = ''
6622
+ self.city = ''
6623
+ self.state = ''
6624
+ end
6625
+ end
6626
+ }
6627
+
6628
+ search_entry {
6629
+ stretchy false
6630
+ # bidirectional data-binding of text to self.filter_value with after_write option
6631
+ text <=> [self, :filter_value,
6632
+ after_write: ->(filter_value) { # execute after write to self.filter_value
6633
+ @unfiltered_contacts ||= @contacts.dup
6634
+ # Unfilter first to remove any previous filters
6635
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
6636
+ # Now, apply filter if entered
6637
+ unless filter_value.empty?
6638
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
6639
+ contact.members.any? do |attribute|
6640
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
6641
+ end
6642
+ end
6643
+ end
6644
+ }
6645
+ ]
6646
+ }
6647
+
6648
+ table {
6649
+ text_column('Name')
6650
+ text_column('Email')
6651
+ text_column('Phone')
6652
+ text_column('City')
6653
+ text_column('State/Province')
6654
+
6655
+ editable true
6656
+ cell_rows <=> [self, :contacts, column_attributes: {'State/Province' => :state}] # explicit data-binding to Model Array with column_attributes mapping for a specific column
6657
+
6658
+ on_changed do |row, type, row_data|
6659
+ puts "Row #{row} #{type}: #{row_data}"
6660
+ end
6661
+ }
6662
+ }
6663
+ }.show
6664
+ end
6665
+ end
6666
+
6667
+ FormTable.new.launch
6668
+ ```
6669
+
6670
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
6671
+
6672
+ ```ruby
6673
+
6674
+ require 'glimmer-dsl-libui'
6675
+
6676
+ class FormTable
6677
+ Contact = Struct.new(:full_name, :email_address, :phone_number, :city_or_town, :state_or_province)
6678
+
6679
+ include Glimmer
6680
+
6681
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
6682
+
6683
+ def initialize
6684
+ @contacts = [
6685
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
6686
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
6687
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
6688
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
6689
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
6690
+ ]
6691
+ end
6692
+
6693
+ def launch
6694
+ window('Contacts', 600, 600) { |w|
6695
+ margined true
6696
+
6697
+ vertical_box {
6698
+ form {
6699
+ stretchy false
6700
+
6701
+ entry {
6702
+ label 'Name'
6703
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6704
+ }
6705
+
6706
+ entry {
6707
+ label 'Email'
6708
+ text <=> [self, :email]
6709
+ }
6710
+
6711
+ entry {
6712
+ label 'Phone'
6713
+ text <=> [self, :phone]
6714
+ }
6715
+
6716
+ entry {
6717
+ label 'City'
6718
+ text <=> [self, :city]
6719
+ }
6720
+
6721
+ entry {
6722
+ label 'State'
6723
+ text <=> [self, :state]
6724
+ }
6725
+ }
6726
+
6727
+ button('Save Contact') {
6728
+ stretchy false
6729
+
6730
+ on_clicked do
6731
+ new_row = [name, email, phone, city, state]
6732
+ if new_row.include?('')
6733
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6734
+ else
6735
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to implicit data-binding
6736
+ @unfiltered_contacts = @contacts.dup
6737
+ self.name = '' # automatically clears name entry through explicit data-binding
6738
+ self.email = ''
6739
+ self.phone = ''
6740
+ self.city = ''
6741
+ self.state = ''
6742
+ end
6743
+ end
6744
+ }
6745
+
6746
+ search_entry {
6747
+ stretchy false
6748
+ # bidirectional data-binding of text to self.filter_value with after_write option
6749
+ text <=> [self, :filter_value,
6750
+ after_write: ->(filter_value) { # execute after write to self.filter_value
6751
+ @unfiltered_contacts ||= @contacts.dup
6752
+ # Unfilter first to remove any previous filters
6753
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
6754
+ # Now, apply filter if entered
6755
+ unless filter_value.empty?
6756
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
6757
+ contact.members.any? do |attribute|
6758
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
6759
+ end
6760
+ end
6761
+ end
6762
+ }
6763
+ ]
6764
+ }
6765
+
6766
+ table {
6767
+ text_column('Name')
6768
+ text_column('Email')
6769
+ text_column('Phone')
6770
+ text_column('City')
6771
+ text_column('State')
6772
+
6773
+ editable true
6774
+ cell_rows <=> [self, :contacts, column_attributes: [:full_name, :email_address, :phone_number, :city_or_town, :state_or_province]] # explicit data-binding to Model Array with column_attributes mapping for all columns
6775
+
6776
+ on_changed do |row, type, row_data|
6777
+ puts "Row #{row} #{type}: #{row_data}"
6778
+ end
6779
+ }
6780
+ }
6781
+ }.show
6782
+ end
6783
+ end
6784
+
6785
+ FormTable.new.launch
6786
+ ```
6787
+
6788
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (with explicit [data-binding](#data-binding) to raw data):
6306
6789
 
6307
6790
  ```ruby
6308
6791
  require 'glimmer-dsl-libui'
@@ -6332,7 +6815,7 @@ class FormTable
6332
6815
 
6333
6816
  entry {
6334
6817
  label 'Name'
6335
- text <=> [self, :name]
6818
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6336
6819
  }
6337
6820
 
6338
6821
  entry {
@@ -6364,8 +6847,8 @@ class FormTable
6364
6847
  if new_row.include?('')
6365
6848
  msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6366
6849
  else
6367
- @data << new_row # automatically inserts a row into the table due to implicit data-binding
6368
- @unfiltered_data = @data.dup
6850
+ data << new_row # automatically inserts a row into the table due to implicit data-binding
6851
+ @unfiltered_data = data.dup
6369
6852
  self.name = '' # automatically clears name entry through explicit data-binding
6370
6853
  self.email = ''
6371
6854
  self.phone = ''
@@ -6377,14 +6860,15 @@ class FormTable
6377
6860
 
6378
6861
  search_entry {
6379
6862
  stretchy false
6380
- text <=> [self, :filter_value, # bidirectional data-binding of text to self.filter_value with after_write option
6863
+ # bidirectional data-binding of text to self.filter_value with after_write option
6864
+ text <=> [self, :filter_value,
6381
6865
  after_write: ->(filter_value) { # execute after write to self.filter_value
6382
- @unfiltered_data ||= @data.dup
6866
+ @unfiltered_data ||= data.dup
6383
6867
  # Unfilter first to remove any previous filters
6384
- self.data = @unfiltered_data # affects table indirectly through explicit data-binding
6868
+ data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
6385
6869
  # Now, apply filter if entered
6386
6870
  unless filter_value.empty?
6387
- self.data = @data.filter do |row_data| # affects table indirectly through explicit data-binding
6871
+ data.filter! do |row_data| # affects table indirectly through implicit data-binding
6388
6872
  row_data.any? do |cell|
6389
6873
  cell.to_s.downcase.include?(filter_value.downcase)
6390
6874
  end
@@ -6400,8 +6884,9 @@ class FormTable
6400
6884
  text_column('Phone')
6401
6885
  text_column('City')
6402
6886
  text_column('State')
6403
-
6404
- cell_rows <=> [self, :data] # explicit data-binding
6887
+
6888
+ editable true
6889
+ cell_rows <=> [self, :data] # explicit data-binding to raw data Array of Arrays
6405
6890
 
6406
6891
  on_changed do |row, type, row_data|
6407
6892
  puts "Row #{row} #{type}: #{row_data}"
@@ -6415,7 +6900,7 @@ end
6415
6900
  FormTable.new.launch
6416
6901
  ```
6417
6902
 
6418
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
6903
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 5 (with implicit [data-binding](#data-binding)):
6419
6904
 
6420
6905
  ```ruby
6421
6906
  require 'glimmer-dsl-libui'
@@ -6503,7 +6988,8 @@ window('Contacts', 600, 600) { |w|
6503
6988
  text_column('City')
6504
6989
  text_column('State')
6505
6990
 
6506
- cell_rows data # implicit data-binding
6991
+ editable true
6992
+ cell_rows data # implicit data-binding to raw data Array of Arrays
6507
6993
 
6508
6994
  on_changed do |row, type, row_data|
6509
6995
  puts "Row #{row} #{type}: #{row_data}"