glimmer-dsl-libui 0.4.10 → 0.4.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/README.md +1071 -219
  4. data/VERSION +1 -1
  5. data/examples/basic_image.rb +5 -3
  6. data/examples/basic_image2.rb +1 -3
  7. data/examples/basic_image3.rb +3 -3
  8. data/examples/basic_image4.rb +0 -3
  9. data/examples/basic_image5.rb +0 -2
  10. data/examples/basic_table_button.rb +54 -30
  11. data/examples/basic_table_button2.rb +34 -0
  12. data/examples/basic_table_color.rb +104 -26
  13. data/examples/basic_table_color2.rb +2 -14
  14. data/examples/basic_table_color3.rb +37 -0
  15. data/examples/basic_table_image.rb +1 -1
  16. data/examples/basic_table_image2.rb +2 -14
  17. data/examples/basic_table_image3.rb +44 -0
  18. data/examples/basic_table_image_text.rb +1 -2
  19. data/examples/basic_table_image_text2.rb +2 -13
  20. data/examples/basic_table_image_text3.rb +44 -0
  21. data/examples/cpu_percentage.rb +1 -1
  22. data/examples/editable_table.rb +1 -1
  23. data/examples/form_table.rb +21 -17
  24. data/examples/form_table2.rb +104 -85
  25. data/examples/form_table3.rb +113 -0
  26. data/examples/form_table4.rb +110 -0
  27. data/examples/form_table5.rb +94 -0
  28. data/examples/meta_example.rb +6 -4
  29. data/glimmer-dsl-libui.gemspec +0 -0
  30. data/lib/glimmer/dsl/libui/control_expression.rb +2 -1
  31. data/lib/glimmer/dsl/libui/shape_expression.rb +2 -2
  32. data/lib/glimmer/dsl/libui/string_expression.rb +2 -1
  33. data/lib/glimmer/libui/attributed_string.rb +3 -2
  34. data/lib/glimmer/libui/control_proxy/column/background_color_column_proxy.rb +4 -0
  35. data/lib/glimmer/libui/control_proxy/image_proxy.rb +90 -10
  36. data/lib/glimmer/libui/control_proxy/table_proxy.rb +95 -29
  37. data/lib/glimmer/libui/control_proxy.rb +4 -2
  38. data/lib/glimmer/libui/data_bindable.rb +8 -3
  39. data/lib/glimmer/libui/shape.rb +3 -2
  40. data/lib/glimmer/libui.rb +2 -2
  41. data/lib/glimmer-dsl-libui.rb +1 -0
  42. metadata +9 -2
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.14
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 self.contacts Modal Array, auto-inferring model attribute names from underscored table column names by convention
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
@@ -235,6 +356,11 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
235
356
  - [Custom Keywords](#custom-keywords)
236
357
  - [Observer Pattern](#observer-pattern)
237
358
  - [Data-Binding](#data-binding)
359
+ - [Bidirectional (Two-Way) Data-Binding](#bidirectional-two-way-data-binding)
360
+ - [Table Data-Binding](#table-data-binding)
361
+ - [Unidirectional (One-Way) Data-Binding](#unidirectional-one-way-data-binding)
362
+ - [Data-Binding API](#data-binding-api)
363
+ - [Data-Binding Gotchas](#data-binding-gotchas)
238
364
  - [API Gotchas](#api-gotchas)
239
365
  - [Original API](#original-api)
240
366
  - [Packaging](#packaging)
@@ -374,10 +500,20 @@ gem install glimmer-dsl-libui
374
500
  Or install via Bundler `Gemfile`:
375
501
 
376
502
  ```ruby
377
- gem 'glimmer-dsl-libui', '~> 0.4.10'
503
+ gem 'glimmer-dsl-libui', '~> 0.4.14'
504
+ ```
505
+
506
+ Test that installation worked by running the [Meta-Example](#examples):
507
+
508
+ ```
509
+ ruby -r glimmer-dsl-libui -e "require 'examples/meta_example'"
378
510
  ```
379
511
 
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.
512
+ Mac | Windows | Linux
513
+ ----|---------|------
514
+ ![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)
515
+
516
+ 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
517
 
382
518
  Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
383
519
 
@@ -452,7 +588,7 @@ Keyword(Args) | Properties | Listeners
452
588
  `about_menu_item` | None | `on_clicked`
453
589
  `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
590
  `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
591
+ `background_color_column` | None | None
456
592
  `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
593
  `button(text as String)` | `text` (`String`) | `on_clicked`
458
594
  `button_column(name as String)` | `enabled` (Boolean) | None
@@ -616,160 +752,150 @@ Note that the `cell_rows` property declaration results in "implicit data-binding
616
752
  - Inserting cell rows: Calling `Array#<<`, `Array#push`, `Array#prepend`, or any insertion/addition `Array` method automatically inserts rows in actual `table` control
617
753
  - Changing cell rows: Calling `Array#[]=`, `Array#map!`, or any update `Array` method automatically updates rows in actual `table` control
618
754
 
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.
755
+ ([explicit data-binding](#data-binding) supports everything available with implicit data-binding too)
743
756
 
744
- Here is an example of a declarative `area` with a stable path (you may copy/paste in [`girb`](#girb-glimmer-irb)):
757
+ Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
745
758
 
746
759
  ```ruby
747
760
  require 'glimmer-dsl-libui'
748
761
 
749
762
  include Glimmer
750
763
 
751
- window('Basic Area', 400, 400) {
764
+ data = [
765
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],
766
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],
767
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],
768
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],
769
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
770
+ ]
771
+
772
+ window('Contacts', 600, 600) { |w|
752
773
  margined true
753
774
 
754
775
  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
776
+ form {
777
+ stretchy false
778
+
779
+ @name_entry = entry {
780
+ label 'Name'
760
781
  }
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
-
772
- ```ruby
782
+
783
+ @email_entry = entry {
784
+ label 'Email'
785
+ }
786
+
787
+ @phone_entry = entry {
788
+ label 'Phone'
789
+ }
790
+
791
+ @city_entry = entry {
792
+ label 'City'
793
+ }
794
+
795
+ @state_entry = entry {
796
+ label 'State'
797
+ }
798
+ }
799
+
800
+ button('Save Contact') {
801
+ stretchy false
802
+
803
+ on_clicked do
804
+ new_row = [@name_entry.text, @email_entry.text, @phone_entry.text, @city_entry.text, @state_entry.text]
805
+ if new_row.include?('')
806
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
807
+ else
808
+ data << new_row # automatically inserts a row into the table due to implicit data-binding
809
+ @unfiltered_data = data.dup
810
+ @name_entry.text = ''
811
+ @email_entry.text = ''
812
+ @phone_entry.text = ''
813
+ @city_entry.text = ''
814
+ @state_entry.text = ''
815
+ end
816
+ end
817
+ }
818
+
819
+ search_entry { |se|
820
+ stretchy false
821
+
822
+ on_changed do
823
+ filter_value = se.text
824
+ @unfiltered_data ||= data.dup
825
+ # Unfilter first to remove any previous filters
826
+ data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
827
+ # Now, apply filter if entered
828
+ unless filter_value.empty?
829
+ data.filter! do |row_data| # affects table indirectly through implicit data-binding
830
+ row_data.any? do |cell|
831
+ cell.to_s.downcase.include?(filter_value.downcase)
832
+ end
833
+ end
834
+ end
835
+ end
836
+ }
837
+
838
+ table {
839
+ text_column('Name')
840
+ text_column('Email')
841
+ text_column('Phone')
842
+ text_column('City')
843
+ text_column('State')
844
+
845
+ editable true
846
+ cell_rows data # implicit data-binding to raw data Array of Arrays
847
+
848
+ on_changed do |row, type, row_data|
849
+ puts "Row #{row} #{type}: #{row_data}"
850
+ end
851
+ }
852
+ }
853
+ }.show
854
+ ```
855
+
856
+ Mac | Windows | Linux
857
+ ----|---------|------
858
+ ![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)
859
+
860
+ Learn more by checking out [examples](#examples).
861
+
862
+ ### Area API
863
+
864
+ The `area` control is a canvas-like control for drawing paths that can be used in one of two ways:
865
+ - 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).
866
+ - 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.
867
+
868
+ 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.
869
+
870
+ Here is an example of a declarative `area` with a stable path (you may copy/paste in [`girb`](#girb-glimmer-irb)):
871
+
872
+ ```ruby
873
+ require 'glimmer-dsl-libui'
874
+
875
+ include Glimmer
876
+
877
+ window('Basic Area', 400, 400) {
878
+ margined true
879
+
880
+ vertical_box {
881
+ area {
882
+ path { # a stable path is added declaratively
883
+ rectangle(0, 0, 400, 400)
884
+
885
+ fill r: 102, g: 102, b: 204, a: 1.0
886
+ }
887
+ }
888
+ }
889
+ }.show
890
+ ```
891
+
892
+ Mac | Windows | Linux
893
+ ----|---------|------
894
+ ![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)
895
+
896
+ 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)):
897
+
898
+ ```ruby
773
899
  require 'glimmer-dsl-libui'
774
900
 
775
901
  include Glimmer
@@ -799,17 +925,23 @@ Check [examples/dynamic_area.rb](#dynamic-area) for a more detailed semi-declara
799
925
  - `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
926
  - `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
927
 
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)
928
+ Mac | Windows | Linux
929
+ ----|---------|------
930
+ ![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
931
 
806
932
  Check [examples/basic_scrolling_area.rb](#basic-scrolling-area) for a more detailed example.
807
933
 
808
934
  #### Area Path Shapes
809
935
 
936
+ `area` can have geometric shapes drawn by adding `path` elements.
937
+
938
+ To add `path` shapes under an `area`, you can do so:
939
+ - Explicitly: by adding `path` under `area` and nesting shapes (e.g. `rectangle`) underneath that share the same `fill`/`stroke`/`transform` properties
940
+ - Implicitly: by adding shapes directly under `area` when the shapes have unique `fill`/`stroke`/`transform` properties ([Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) automatically constructs `path`s as intermediary parents for shapes directly added under `area`)
941
+
810
942
  `path` can receive a `draw_fill_mode` argument that can accept values `:winding` or `:alternate` and defaults to `:winding`.
811
943
 
812
- Available nested `path` shapes:
944
+ Available `path` shapes (that can be nested explicitly under `path` or implicitly under `area` directly):
813
945
  - `rectangle(x as Numeric, y as Numeric, width as Numeric, height as Numeric)`
814
946
  - `square(x as Numeric, y as Numeric, length as Numeric)`
815
947
  - `arc(x_center as Numeric, y_center as Numeric, radius as Numeric, start_angle as Numeric, sweep as Numeric, is_negative as Boolean)`
@@ -895,7 +1027,8 @@ Given that it is very new and is not a [libui](https://github.com/andlabs/libui)
895
1027
  - It only supports the `.png` file format.
896
1028
  - [libui](https://github.com/andlabs/libui) pixel-by-pixel rendering performance is slow.
897
1029
  - Including an `image` inside an `area` `on_draw` listener improves performance due to not retaining pixel/line data in memory.
898
- - Supplying `width` and `height` (2nd and 3rd arguments) greatly improves performance when shrinking image.
1030
+ - Supplying `width` and `height` options greatly improves performance when shrinking image (e.g. `image('somefile.png', width: 24, height: 24)`). You can also supply one of the two dimensions, and the other one gets calculated automatically while preserving original aspect ratio (e.g. `image('somefile.png', height: 24)`)
1031
+ - [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) lets you optionally specify `x` and `y` in addition to `file`, `width` and `height` (5 arguments total) to offset image location.
899
1032
 
900
1033
  Currently, it is recommended to use `image` with very small `width` and `height` values only (e.g. 24x24).
901
1034
 
@@ -914,7 +1047,11 @@ include Glimmer
914
1047
 
915
1048
  window('Basic Image', 96, 96) {
916
1049
  area {
917
- image(File.expand_path('icons/glimmer.png', __dir__), 96, 96)
1050
+ image(File.expand_path('icons/glimmer.png', __dir__), height: 96) # width is automatically calculated from height while preserving original aspect ratio
1051
+ # image(File.expand_path('icons/glimmer.png', __dir__), width: 96, height: 96) # you can specify both width and height options
1052
+ # image(File.expand_path('icons/glimmer.png', __dir__), 96, 96) # you can specify width, height as args
1053
+ # image(File.expand_path('../icons/glimmer.png', __dir__), 0, 0, 96, 96) # you can specify x, y, width, height args as alternative
1054
+ # image(File.expand_path('../icons/glimmer.png', __dir__), x: 0, y: 0, width: 96, height: 96) # you can specify x, y, width, height options as alternative
918
1055
  }
919
1056
  }.show
920
1057
  ```
@@ -946,6 +1083,8 @@ window('Basic Image', 96, 96) {
946
1083
  area {
947
1084
  image {
948
1085
  file File.expand_path('icons/glimmer.png', __dir__)
1086
+ # x 0 # default
1087
+ # y 0 # default
949
1088
  width 96
950
1089
  height 96
951
1090
  }
@@ -1217,6 +1356,7 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t
1217
1356
  - When destroying a control nested under a `horizontal_box` or `vertical_box`, it is automatically deleted from the box's children
1218
1357
  - When destroying a control nested under a `form`, it is automatically deleted from the form's children
1219
1358
  - When destroying a control nested under a `window` or `group`, it is automatically unset as their child to allow successful destruction
1359
+ - When destroying a control that has a data-binding to a model attribute, the data-binding observer registration is automatically deregistered
1220
1360
  - 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
1361
  - 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
1362
  - 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`)
@@ -1227,7 +1367,8 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t
1227
1367
  - Automatically provide shifted `:key` characters in `area_key_event` provided in `area` key listeners `on_key_event`, `on_key_down`, and `on_key_up`
1228
1368
  - `scrolling_area` `width` and `height` default to main window width and height if not specified.
1229
1369
  - `scrolling_area` `#scroll_to` 3rd and 4th arguments (`width` and `height`) default to main window width and height if not specified.
1230
- - `area` paths are specified declaratively with figures underneath (e.g. `rectangle`) and `area` draw listener is automatically generated
1370
+ - `area` paths are specified declaratively with shapes/figures underneath (e.g. `rectangle`), and `area` draw listener is automatically generated
1371
+ - `area` path shapes can be added directly under `area` without declaring `path` explicitly as a convenient shorthand
1231
1372
  - Observe figure properties (e.g. `rectangle` `width`) for changes and automatically redraw containing area accordingly
1232
1373
  - Observe `path` `fill` and `stroke` hashes for changes and automatically redraw containing area accordingly
1233
1374
  - Observe `text` and `string` properties for changes and automatically redraw containing area accordingly
@@ -1382,6 +1523,8 @@ Data-binding supports utilizing the [MVP (Model View Presenter)](https://en.wiki
1382
1523
 
1383
1524
  ![MVP](https://www.researchgate.net/profile/Gilles-Perrouin/publication/320249584/figure/fig8/AS:668260987068418@1536337243385/Model-view-presenter-architecture.png)
1384
1525
 
1526
+ #### Bidirectional (Two-Way) Data-Binding
1527
+
1385
1528
  [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports bidirectional (two-way) data-binding of the following controls/properties via the `<=>` operator (indicating data is moving in both directions between View and Model):
1386
1529
  - `checkbox`: `checked`
1387
1530
  - `check_menu_item`: `checked`
@@ -1422,6 +1565,162 @@ entry {
1422
1565
 
1423
1566
  That is data-binding `entered_text` attribute on `self` to `entry` `text` property and printing text after write to the model.
1424
1567
 
1568
+ ##### Table Data-Binding
1569
+
1570
+ One note about `table` `cell_rows` data-binding is that it works with either:
1571
+ - Raw data `Array` (rows) of `Array`s (column cells)
1572
+ - Model `Array` (rows) of objects having attributes (column cells) matching the underscored names of `table` columns by convention. Model attribute names can be overridden when needed by passing an `Array` enumerating all mapped model attributes in the order of `table` columns or alternatively a `Hash` mapping only the column names that have model attribute names different from their table column underscored version.
1573
+
1574
+ Example of `table` implicit data-binding of `cell_rows` to raw data `Array` of `Array`s (you may copy/paste in [`girb`](#girb-glimmer-irb)):
1575
+
1576
+ ```ruby
1577
+ require 'glimmer-dsl-libui'
1578
+
1579
+ include Glimmer
1580
+
1581
+ data = [
1582
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],
1583
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],
1584
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],
1585
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],
1586
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
1587
+ ]
1588
+
1589
+ window('Contacts', 600, 600) {
1590
+ table {
1591
+ text_column('Name')
1592
+ text_column('Email')
1593
+ text_column('Phone')
1594
+ text_column('City')
1595
+ text_column('State')
1596
+
1597
+ cell_rows data
1598
+ }
1599
+ }.show
1600
+ ```
1601
+
1602
+ Example of `table` explicit data-binding of `cell_rows` to Model `Array` (you may copy/paste in [`girb`](#girb-glimmer-irb)):
1603
+
1604
+ ```ruby
1605
+ require 'glimmer-dsl-libui'
1606
+
1607
+ class SomeTable
1608
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
1609
+
1610
+ include Glimmer
1611
+
1612
+ attr_accessor :contacts
1613
+
1614
+ def initialize
1615
+ @contacts = [
1616
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
1617
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
1618
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
1619
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
1620
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
1621
+ ]
1622
+ end
1623
+
1624
+ def launch
1625
+ window('Contacts', 600, 200) {
1626
+ table {
1627
+ text_column('Name')
1628
+ text_column('Email')
1629
+ text_column('Phone')
1630
+ text_column('City')
1631
+ text_column('State')
1632
+
1633
+ cell_rows <=> [self, :contacts] # explicit data-binding to self.contacts Model Array, auto-inferring model attribute names from underscored table column names by convention
1634
+ }
1635
+ }.show
1636
+ end
1637
+ end
1638
+
1639
+ SomeTable.new.launch
1640
+ ```
1641
+
1642
+ Example of `table` explicit data-binding of `cell_rows` to Model `Array` with `column_attributes` `Hash` mapping for custom column names (you may copy/paste in [`girb`](#girb-glimmer-irb)):
1643
+
1644
+ ```ruby
1645
+ require 'glimmer-dsl-libui'
1646
+
1647
+ class SomeTable
1648
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
1649
+
1650
+ include Glimmer
1651
+
1652
+ attr_accessor :contacts
1653
+
1654
+ def initialize
1655
+ @contacts = [
1656
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
1657
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
1658
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
1659
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
1660
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
1661
+ ]
1662
+ end
1663
+
1664
+ def launch
1665
+ window('Contacts', 600, 200) {
1666
+ table {
1667
+ text_column('Name')
1668
+ text_column('Email')
1669
+ text_column('Phone')
1670
+ text_column('City/Town')
1671
+ text_column('State/Province')
1672
+
1673
+ cell_rows <=> [self, :contacts, column_attributes: {'City/Town' => :city, 'State/Province' => :state}]
1674
+ }
1675
+ }.show
1676
+ end
1677
+ end
1678
+
1679
+ SomeTable.new.launch
1680
+ ```
1681
+
1682
+ Example of `table` explicit data-binding of `cell_rows` to Model `Array` with complete `column_attributes` `Array` mapping (you may copy/paste in [`girb`](#girb-glimmer-irb)):
1683
+
1684
+ ```ruby
1685
+ require 'glimmer-dsl-libui'
1686
+
1687
+ class SomeTable
1688
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
1689
+
1690
+ include Glimmer
1691
+
1692
+ attr_accessor :contacts
1693
+
1694
+ def initialize
1695
+ @contacts = [
1696
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
1697
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
1698
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
1699
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
1700
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
1701
+ ]
1702
+ end
1703
+
1704
+ def launch
1705
+ window('Contacts', 600, 200) {
1706
+ table {
1707
+ text_column('Full Name')
1708
+ text_column('Email Address')
1709
+ text_column('Phone Number')
1710
+ text_column('City or Town')
1711
+ text_column('State or Province')
1712
+
1713
+ cell_rows <=> [self, :contacts, column_attributes: [:name, :email, :phone, :city, :state]]
1714
+ }
1715
+ }.show
1716
+ end
1717
+ end
1718
+
1719
+ SomeTable.new.launch
1720
+ ```
1721
+
1722
+ #### Unidirectional (One-Way) Data-Binding
1723
+
1425
1724
  [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports unidirectional (one-way) data-binding of any control/shape/attributed-string property via the `<=` operator (indicating data is moving from the right side, which is the Model, to the left side, which is the GUI View object).
1426
1725
 
1427
1726
  Example of unidirectional data-binding:
@@ -1444,6 +1743,8 @@ window {
1444
1743
 
1445
1744
  That is data-binding the `window` `title` property to the `score` attribute of a `@game`, but converting on read from the Model to a `String`.
1446
1745
 
1746
+ #### Data-Binding API
1747
+
1447
1748
  To summarize the data-binding API:
1448
1749
  - `view_property <=> [model, attribute, *read_or_write_options]`: Bidirectional (two-way) data-binding to Model attribute accessor
1449
1750
  - `view_property <= [model, attribute, *read_only_options]`: Unidirectional (one-way) data-binding to Model attribute reader
@@ -1474,11 +1775,13 @@ entry {
1474
1775
  }
1475
1776
  ```
1476
1777
 
1477
- Data-binding gotchas:
1778
+ Learn more from data-binding usage in [Login](#login) (4 data-binding versions), [Basic Entry](#basic-entry), [Form](#form), [Form Table](#form-table) (5 data-binding versions), [Method-Based Custom Keyword](#method-based-custom-keyword), [Snake](#snake) and [Tic Tac Toe](#tic_tac_toe) examples.
1779
+
1780
+ #### Data-Binding Gotchas
1781
+
1478
1782
  - Never data-bind a control property to an attribute on the same view object with the same exact name (e.g. binding `entry` `text` property to `self` `text` attribute) as it would conflict with it. Instead, data-bind view property to an attribute with a different name on the view object or with the same name, but on a presenter or model object (e.g. data-bind `entry` `text` to `self` `legal_text` attribute or to `contract` model `text` attribute)
1479
1783
  - Data-binding a property utilizes the control's listener associated with the property (e.g. `on_changed` for `entry` `text`), so you cannot hook into the listener directly anymore as that would negate data-binding. Instead, you can add an `after_write: ->(val) {}` option to perform something on trigger of the control listener instead.
1480
-
1481
- Learn more from data-binding usage in [Login](#login) (4 data-binding versions), [Basic Entry](#basic-entry), [Form](#form), [Form Table](#form-table), [Method-Based Custom Keyword](#method-based-custom-keyword), [Snake](#snake) and [Tic Tac Toe](#tic_tac_toe) examples.
1784
+ - Data-binding a View control to another View control directly is not a good idea. Instead, data-bind both View controls to the same Presenter/Model attribute, and that keeps them in sync while keeping the code decoupled.
1482
1785
 
1483
1786
  ### API Gotchas
1484
1787
 
@@ -1491,6 +1794,7 @@ Learn more from data-binding usage in [Login](#login) (4 data-binding versions),
1491
1794
  - `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
1795
  - 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
1796
  - As per the code of [examples/basic_transform.rb](#basic-transform), Windows requires different ordering of transforms than Mac and Linux.
1797
+ - `scrolling_area#scroll_to` does not seem to work on Windows and Linux, but works fine on Mac
1494
1798
 
1495
1799
  ### Original API
1496
1800
 
@@ -2804,7 +3108,44 @@ UI.main
2804
3108
  UI.quit
2805
3109
  ```
2806
3110
 
2807
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3111
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (passing file url as image):
3112
+
3113
+ ```ruby
3114
+ # frozen_string_literal: true
3115
+
3116
+ # NOTE:
3117
+ # This example displays images that can be freely downloaded from the Studio Ghibli website.
3118
+
3119
+ require 'glimmer-dsl-libui'
3120
+
3121
+ include Glimmer
3122
+
3123
+ IMAGE_ROWS = []
3124
+
3125
+ 50.times do |i|
3126
+ url = format('https://www.ghibli.jp/gallery/thumb-redturtle%03d.png', (i + 1))
3127
+ puts "Processing Image: #{url}"; $stdout.flush # for Windows
3128
+ IMAGE_ROWS << [url] # array of one column cell
3129
+ rescue StandardError => e
3130
+ warn url, e.message
3131
+ end
3132
+
3133
+ window('The Red Turtle', 310, 350, false) {
3134
+ horizontal_box {
3135
+ table {
3136
+ image_column('www.ghibli.jp/works/red-turtle')
3137
+
3138
+ cell_rows IMAGE_ROWS
3139
+ }
3140
+ }
3141
+
3142
+ on_closing do
3143
+ puts 'Bye Bye'
3144
+ end
3145
+ }.show
3146
+ ```
3147
+
3148
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (automatic construction of `image`):
2808
3149
 
2809
3150
  ```ruby
2810
3151
  # NOTE:
@@ -2839,7 +3180,7 @@ window('The Red Turtle', 310, 350, false) {
2839
3180
  }.show
2840
3181
  ```
2841
3182
 
2842
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (manual construction of `image` from `image_part`):
3183
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (manual construction of `image` from `image_part`):
2843
3184
 
2844
3185
  ```ruby
2845
3186
  # NOTE:
@@ -2907,9 +3248,11 @@ Mac | Windows | Linux
2907
3248
  ----|---------|------
2908
3249
  ![glimmer-dsl-libui-mac-basic-table-image-text.png](images/glimmer-dsl-libui-mac-basic-table-image-text.png) | ![glimmer-dsl-libui-windows-basic-table-image-text.png](images/glimmer-dsl-libui-windows-basic-table-image-text.png) | ![glimmer-dsl-libui-linux-basic-table-image-text.png](images/glimmer-dsl-libui-linux-basic-table-image-text.png)
2909
3250
 
2910
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3251
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (passing file url as image):
2911
3252
 
2912
3253
  ```ruby
3254
+ # frozen_string_literal: true
3255
+
2913
3256
  # NOTE:
2914
3257
  # This example displays images that can be freely downloaded from the Studio Ghibli website.
2915
3258
 
@@ -2923,8 +3266,7 @@ IMAGE_ROWS = []
2923
3266
  url = format('https://www.ghibli.jp/gallery/thumb-redturtle%03d.png', (i + 1))
2924
3267
  puts "Processing Image: #{url}"; $stdout.flush # for Windows
2925
3268
  text = url.sub('https://www.ghibli.jp/gallery/thumb-redturtle', '').sub('.png', '')
2926
- img = image(url)
2927
- IMAGE_ROWS << [[img, text], [img, text]] # cell values are dual-element arrays
3269
+ IMAGE_ROWS << [[url, text], [url, text]] # cell values are dual-element arrays
2928
3270
  rescue StandardError => e
2929
3271
  warn url, e.message
2930
3272
  end
@@ -2943,15 +3285,13 @@ window('The Red Turtle', 670, 350) {
2943
3285
  }.show
2944
3286
  ```
2945
3287
 
2946
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (manual construction of `image` from `image_part`):
3288
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (automatic construction of `image`):
2947
3289
 
2948
3290
  ```ruby
2949
3291
  # NOTE:
2950
3292
  # This example displays images that can be freely downloaded from the Studio Ghibli website.
2951
3293
 
2952
3294
  require 'glimmer-dsl-libui'
2953
- require 'chunky_png'
2954
- require 'open-uri'
2955
3295
 
2956
3296
  include Glimmer
2957
3297
 
@@ -2959,9 +3299,47 @@ IMAGE_ROWS = []
2959
3299
 
2960
3300
  5.times do |i|
2961
3301
  url = format('https://www.ghibli.jp/gallery/thumb-redturtle%03d.png', (i + 1))
2962
- puts "Processing Image: #{url}"
2963
- f = URI.open(url)
2964
- canvas = ChunkyPNG::Canvas.from_io(f)
3302
+ puts "Processing Image: #{url}"; $stdout.flush # for Windows
3303
+ text = url.sub('https://www.ghibli.jp/gallery/thumb-redturtle', '').sub('.png', '')
3304
+ img = image(url)
3305
+ IMAGE_ROWS << [[img, text], [img, text]] # cell values are dual-element arrays
3306
+ rescue StandardError => e
3307
+ warn url, e.message
3308
+ end
3309
+
3310
+ window('The Red Turtle', 670, 350) {
3311
+ horizontal_box {
3312
+ table {
3313
+ image_text_column('image/number')
3314
+ image_text_column('image/number (editable)') {
3315
+ editable true
3316
+ }
3317
+
3318
+ cell_rows IMAGE_ROWS
3319
+ }
3320
+ }
3321
+ }.show
3322
+ ```
3323
+
3324
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (manual construction of `image` from `image_part`):
3325
+
3326
+ ```ruby
3327
+ # NOTE:
3328
+ # This example displays images that can be freely downloaded from the Studio Ghibli website.
3329
+
3330
+ require 'glimmer-dsl-libui'
3331
+ require 'chunky_png'
3332
+ require 'open-uri'
3333
+
3334
+ include Glimmer
3335
+
3336
+ IMAGE_ROWS = []
3337
+
3338
+ 5.times do |i|
3339
+ url = format('https://www.ghibli.jp/gallery/thumb-redturtle%03d.png', (i + 1))
3340
+ puts "Processing Image: #{url}"
3341
+ f = URI.open(url)
3342
+ canvas = ChunkyPNG::Canvas.from_io(f)
2965
3343
  f.close
2966
3344
  data = canvas.to_rgba_stream
2967
3345
  width = canvas.width
@@ -3012,22 +3390,28 @@ Mac | Windows | Linux
3012
3390
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
3013
3391
 
3014
3392
  ```ruby
3015
- # frozen_string_literal: true
3016
-
3017
3393
  require 'glimmer-dsl-libui'
3018
3394
 
3019
3395
  class BasicTableButton
3396
+ BasicAnimal = Struct.new(:name, :sound)
3397
+
3398
+ class Animal < BasicAnimal
3399
+ def action
3400
+ 'delete'
3401
+ end
3402
+ end
3403
+
3020
3404
  include Glimmer
3021
3405
 
3022
- attr_accessor :data
3406
+ attr_accessor :animals
3023
3407
 
3024
3408
  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]
3409
+ @animals = [
3410
+ Animal.new('cat', 'meow'),
3411
+ Animal.new('dog', 'woof'),
3412
+ Animal.new('chicken', 'cock-a-doodle-doo'),
3413
+ Animal.new('horse', 'neigh'),
3414
+ Animal.new('cow', 'moo'),
3031
3415
  ]
3032
3416
  end
3033
3417
 
@@ -3040,17 +3424,19 @@ class BasicTableButton
3040
3424
  button_column('Action') {
3041
3425
  on_clicked do |row|
3042
3426
  # Option 1: direct data deletion is the simpler solution
3043
- # @data.delete_at(row) # automatically deletes actual table row due to explicit data-binding
3427
+ # @animals.delete_at(row) # automatically deletes actual table row due to explicit data-binding
3044
3428
 
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
3429
+ # Option 2: cloning only to demonstrate table row deletion upon explicit setting of animals attribute (cloning is not recommended beyond demonstrating this point)
3430
+ new_animals = @animals.clone
3431
+ new_animals.delete_at(row)
3432
+ self.animals = new_animals # automatically loses deleted table row due to explicit data-binding
3049
3433
  end
3050
3434
  }
3051
3435
 
3052
- cell_rows <=> [self, :data] # explicit data-binding of table cell_rows to self.data
3053
3436
 
3437
+ cell_rows <= [self, :animals, column_attributes: {'Animal' => :name, 'Description' => :sound}]
3438
+
3439
+ # explicit unidirectional data-binding of table cell_rows to self.animals
3054
3440
  on_changed do |row, type, row_data|
3055
3441
  puts "Row #{row} #{type}: #{row_data}"
3056
3442
  $stdout.flush
@@ -3273,16 +3659,126 @@ Mac | Windows | Linux
3273
3659
  ----|---------|------
3274
3660
  ![glimmer-dsl-libui-mac-basic-table-color.png](images/glimmer-dsl-libui-mac-basic-table-color.png) | ![glimmer-dsl-libui-windows-basic-table-color.png](images/glimmer-dsl-libui-windows-basic-table-color.png) | ![glimmer-dsl-libui-linux-basic-table-color.png](images/glimmer-dsl-libui-linux-basic-table-color.png)
3275
3661
 
3276
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3662
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding) to model rows using a presenter):
3277
3663
 
3278
3664
  ```ruby
3279
- # frozen_string_literal: true
3665
+ require 'glimmer-dsl-libui'
3666
+
3667
+ class BasicTableColor
3668
+ Animal = Struct.new(:name, :sound, :mammal)
3669
+
3670
+ class AnimalPresenter < Animal
3671
+ def name_color
3672
+ color = case name
3673
+ when 'cat'
3674
+ :red
3675
+ when 'dog'
3676
+ :yellow
3677
+ when 'chicken'
3678
+ :beige
3679
+ when 'horse'
3680
+ :purple
3681
+ when 'cow'
3682
+ :gray
3683
+ end
3684
+ [name, color]
3685
+ end
3686
+
3687
+ def sound_color
3688
+ color = case name
3689
+ when 'cat', 'chicken', 'cow'
3690
+ :blue
3691
+ when 'dog', 'horse'
3692
+ {r: 240, g: 32, b: 32}
3693
+ end
3694
+ [sound, color]
3695
+ end
3696
+
3697
+ def mammal_description_color
3698
+ color = case name
3699
+ when 'cat', 'dog', 'horse', 'cow'
3700
+ :green
3701
+ when 'chicken'
3702
+ :red
3703
+ end
3704
+ [mammal, 'mammal', color]
3705
+ end
3706
+
3707
+ def image_description_color
3708
+ color = case name
3709
+ when 'cat', 'dog', 'horse'
3710
+ :dark_blue
3711
+ when 'chicken'
3712
+ :beige
3713
+ when 'cow'
3714
+ :brown
3715
+ end
3716
+ [img, 'Glimmer', color]
3717
+ end
3718
+
3719
+ def img
3720
+ # scale image to 24x24 (can be passed as file path String only instead of Array to avoid scaling)
3721
+ [File.expand_path('../icons/glimmer.png', __dir__), 24, 24]
3722
+ end
3723
+
3724
+ def background_color
3725
+ case name
3726
+ when 'cat'
3727
+ {r: 255, g: 120, b: 0, a: 0.5}
3728
+ when 'dog'
3729
+ :skyblue
3730
+ when 'chicken'
3731
+ {r: 5, g: 120, b: 110}
3732
+ when 'horse'
3733
+ '#13a1fb'
3734
+ when 'cow'
3735
+ 0x12ff02
3736
+ end
3737
+ end
3738
+ end
3739
+
3740
+ include Glimmer
3741
+
3742
+ attr_accessor :animals
3743
+
3744
+ def initialize
3745
+ @animals = [
3746
+ AnimalPresenter.new('cat', 'meow', true),
3747
+ AnimalPresenter.new('dog', 'woof', true),
3748
+ AnimalPresenter.new('chicken', 'cock-a-doodle-doo', false),
3749
+ AnimalPresenter.new('horse', 'neigh', true),
3750
+ AnimalPresenter.new('cow', 'moo', true),
3751
+ ]
3752
+ end
3753
+
3754
+ def launch
3755
+ window('Animals', 500, 200) {
3756
+ horizontal_box {
3757
+ table {
3758
+ text_color_column('Animal')
3759
+ text_color_column('Sound')
3760
+ checkbox_text_color_column('Description')
3761
+ image_text_color_column('GUI')
3762
+ background_color_column # must always be the last column and always expects data-binding model attribute `background_color` when binding to Array of models
3763
+
3764
+ cell_rows <= [self, :animals, column_attributes: {'Animal' => :name_color, 'Sound' => :sound_color, 'Description' => :mammal_description_color, 'GUI' => :image_description_color}]
3765
+ }
3766
+ }
3767
+ }.show
3768
+ end
3769
+ end
3770
+
3771
+ BasicTableColor.new.launch
3772
+ ```
3280
3773
 
3774
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (with implicit [data-binding](#data-binding) to raw data rows):
3775
+
3776
+ ```ruby
3281
3777
  require 'glimmer-dsl-libui'
3282
3778
 
3283
3779
  include Glimmer
3284
3780
 
3285
- img = image(File.expand_path('../icons/glimmer.png', __dir__), 24, 24)
3781
+ img = [File.expand_path('../icons/glimmer.png', __dir__), 24, 24] # scales image to 24x24 (can be passed as file path String only instead of Array to avoid scaling)
3286
3782
 
3287
3783
  data = [
3288
3784
  [['cat', :red] , ['meow', :blue] , [true, 'mammal', :green], [img, 'Glimmer', :dark_blue], {r: 255, g: 120, b: 0, a: 0.5}],
@@ -3299,7 +3795,7 @@ window('Animals', 500, 200) {
3299
3795
  text_color_column('Sound')
3300
3796
  checkbox_text_color_column('Description')
3301
3797
  image_text_color_column('GUI')
3302
- background_color_column('Mammal')
3798
+ background_color_column # must be the last column
3303
3799
 
3304
3800
  cell_rows data
3305
3801
  }
@@ -3307,7 +3803,7 @@ window('Animals', 500, 200) {
3307
3803
  }.show
3308
3804
  ```
3309
3805
 
3310
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (manual construction of [libui](https://github.com/andlabs/libui) `image` from `image_part`):
3806
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (with implicit [data-binding](#data-binding) to raw data rows and manual construction of [libui](https://github.com/andlabs/libui) `image` from `image_part`):
3311
3807
 
3312
3808
  ```ruby
3313
3809
  require 'glimmer-dsl-libui'
@@ -3341,7 +3837,7 @@ window('Animals', 500, 200) {
3341
3837
  text_color_column('Sound')
3342
3838
  checkbox_text_color_column('Description')
3343
3839
  image_text_color_column('GUI')
3344
- background_color_column('Mammal')
3840
+ background_color_column
3345
3841
 
3346
3842
  cell_rows data
3347
3843
  }
@@ -3483,9 +3979,9 @@ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/
3483
3979
  ruby -r glimmer-dsl-libui -e "require 'examples/basic_scrolling_area'"
3484
3980
  ```
3485
3981
 
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)
3982
+ Mac | Windows | Linux
3983
+ ----|---------|------
3984
+ ![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
3985
 
3490
3986
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3491
3987
 
@@ -3572,6 +4068,8 @@ BasicScrollingArea.new.launch
3572
4068
 
3573
4069
  #### Basic Image
3574
4070
 
4071
+ Please note the caveats of [Area Image](#area-image) **(Alpha Feature)** with regards to this example.
4072
+
3575
4073
  [examples/basic_image.rb](examples/basic_image.rb)
3576
4074
 
3577
4075
  Run with this command from the root of the project if you cloned the project:
@@ -3607,7 +4105,11 @@ window('Basic Image', 96, 96) {
3607
4105
  # image pixel rendered. Check basic_image2.rb for a faster alternative using on_draw manually.
3608
4106
  #
3609
4107
  # It is recommended to pass width/height args to shrink image and achieve faster performance.
3610
- image(File.expand_path('../icons/glimmer.png', __dir__), 96, 96)
4108
+ image(File.expand_path('../icons/glimmer.png', __dir__), height: 96) # width is automatically calculated from height while preserving original aspect ratio
4109
+ # image(File.expand_path('../icons/glimmer.png', __dir__), width: 96, height: 96) # you can specify both width, height options as alternative
4110
+ # image(File.expand_path('../icons/glimmer.png', __dir__), 96, 96) # you can specify width, height args as alternative
4111
+ # image(File.expand_path('../icons/glimmer.png', __dir__), 0, 0, 96, 96) # you can specify x, y, width, height args as alternative
4112
+ # image(File.expand_path('../icons/glimmer.png', __dir__), x: 0, y: 0, width: 96, height: 96) # you can specify x, y, width, height options as alternative
3611
4113
  }
3612
4114
  }.show
3613
4115
  ```
@@ -3615,8 +4117,6 @@ window('Basic Image', 96, 96) {
3615
4117
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (better performance via `on_draw`):
3616
4118
 
3617
4119
  ```ruby
3618
- # frozen_string_literal: true
3619
-
3620
4120
  require 'glimmer-dsl-libui'
3621
4121
 
3622
4122
  include Glimmer
@@ -3624,7 +4124,7 @@ include Glimmer
3624
4124
  window('Basic Image', 96, 96) {
3625
4125
  area {
3626
4126
  on_draw do |area_draw_params|
3627
- image(File.expand_path('../icons/glimmer.png', __dir__), 96, 96)
4127
+ image(File.expand_path('../icons/glimmer.png', __dir__), height: 96)
3628
4128
  end
3629
4129
  }
3630
4130
  }.show
@@ -3633,8 +4133,6 @@ window('Basic Image', 96, 96) {
3633
4133
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (explicit properties):
3634
4134
 
3635
4135
  ```ruby
3636
- # frozen_string_literal: true
3637
-
3638
4136
  require 'glimmer-dsl-libui'
3639
4137
 
3640
4138
  include Glimmer
@@ -3651,7 +4149,9 @@ window('Basic Image', 96, 96) {
3651
4149
  # It is recommended to pass width/height args to shrink image and achieve faster performance.
3652
4150
  image {
3653
4151
  file File.expand_path('../icons/glimmer.png', __dir__)
3654
- width 96
4152
+ # x 0 # default
4153
+ # y 0 # default
4154
+ # width 96 # gets calculated from height while preserving original aspect ratio of 512x512
3655
4155
  height 96
3656
4156
  }
3657
4157
  }
@@ -3661,8 +4161,6 @@ window('Basic Image', 96, 96) {
3661
4161
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (better performance with `on_draw` when setting explicit properties):
3662
4162
 
3663
4163
  ```ruby
3664
- # frozen_string_literal: true
3665
-
3666
4164
  require 'glimmer-dsl-libui'
3667
4165
 
3668
4166
  include Glimmer
@@ -3672,7 +4170,6 @@ window('Basic Image', 96, 96) {
3672
4170
  on_draw do |area_draw_params|
3673
4171
  image {
3674
4172
  file File.expand_path('../icons/glimmer.png', __dir__)
3675
- width 96
3676
4173
  height 96
3677
4174
  }
3678
4175
  end
@@ -4822,9 +5319,9 @@ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/
4822
5319
  ruby -r glimmer-dsl-libui -e "require 'examples/button_counter'"
4823
5320
  ```
4824
5321
 
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)
5322
+ Mac | Windows | Linux
5323
+ ----|---------|------
5324
+ ![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
5325
 
4829
5326
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
4830
5327
 
@@ -5540,7 +6037,7 @@ Glimmer::LibUI.timer(1) do
5540
6037
  data[0][2] = cpu_percentage_value
5541
6038
  end
5542
6039
 
5543
- window('CPU Percentage', 400, 200) {
6040
+ window('CPU Percentage', 400, 50) {
5544
6041
  vertical_box {
5545
6042
  table {
5546
6043
  text_column('Name')
@@ -6263,8 +6760,8 @@ window('Editable animal sounds', 300, 200) {
6263
6760
  text_column('Animal')
6264
6761
  text_column('Description')
6265
6762
 
6266
- cell_rows data
6267
6763
  editable true
6764
+ cell_rows data
6268
6765
 
6269
6766
  on_changed do |row, type, row_data| # fires on all changes (even ones happening through data array)
6270
6767
  puts "Row #{row} #{type}: #{row_data}"
@@ -6302,7 +6799,359 @@ Mac | Windows | Linux
6302
6799
  ----|---------|------
6303
6800
  ![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
6801
 
6305
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
6802
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
6803
+
6804
+ ```ruby
6805
+ require 'glimmer-dsl-libui'
6806
+
6807
+ class FormTable
6808
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
6809
+
6810
+ include Glimmer
6811
+
6812
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
6813
+
6814
+ def initialize
6815
+ @contacts = [
6816
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
6817
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
6818
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
6819
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
6820
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
6821
+ ]
6822
+ end
6823
+
6824
+ def launch
6825
+ window('Contacts', 600, 600) { |w|
6826
+ margined true
6827
+
6828
+ vertical_box {
6829
+ form {
6830
+ stretchy false
6831
+
6832
+ entry {
6833
+ label 'Name'
6834
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6835
+ }
6836
+
6837
+ entry {
6838
+ label 'Email'
6839
+ text <=> [self, :email]
6840
+ }
6841
+
6842
+ entry {
6843
+ label 'Phone'
6844
+ text <=> [self, :phone]
6845
+ }
6846
+
6847
+ entry {
6848
+ label 'City'
6849
+ text <=> [self, :city]
6850
+ }
6851
+
6852
+ entry {
6853
+ label 'State'
6854
+ text <=> [self, :state]
6855
+ }
6856
+ }
6857
+
6858
+ button('Save Contact') {
6859
+ stretchy false
6860
+
6861
+ on_clicked do
6862
+ new_row = [name, email, phone, city, state]
6863
+ if new_row.include?('')
6864
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6865
+ else
6866
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to explicit data-binding
6867
+ @unfiltered_contacts = @contacts.dup
6868
+ self.name = '' # automatically clears name entry through explicit data-binding
6869
+ self.email = ''
6870
+ self.phone = ''
6871
+ self.city = ''
6872
+ self.state = ''
6873
+ end
6874
+ end
6875
+ }
6876
+
6877
+ search_entry {
6878
+ stretchy false
6879
+ # bidirectional data-binding of text to self.filter_value with after_write option
6880
+ text <=> [self, :filter_value,
6881
+ after_write: ->(filter_value) { # execute after write to self.filter_value
6882
+ @unfiltered_contacts ||= @contacts.dup
6883
+ # Unfilter first to remove any previous filters
6884
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
6885
+ # Now, apply filter if entered
6886
+ unless filter_value.empty?
6887
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
6888
+ contact.members.any? do |attribute|
6889
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
6890
+ end
6891
+ end
6892
+ end
6893
+ }
6894
+ ]
6895
+ }
6896
+
6897
+ table {
6898
+ text_column('Name')
6899
+ text_column('Email')
6900
+ text_column('Phone')
6901
+ text_column('City')
6902
+ text_column('State')
6903
+
6904
+ editable true
6905
+ cell_rows <=> [self, :contacts] # explicit data-binding to self.contacts Model Array, auto-inferring model attribute names from underscored table column names by convention
6906
+
6907
+ on_changed do |row, type, row_data|
6908
+ puts "Row #{row} #{type}: #{row_data}"
6909
+ end
6910
+ }
6911
+ }
6912
+ }.show
6913
+ end
6914
+ end
6915
+
6916
+ FormTable.new.launch
6917
+ ```
6918
+
6919
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
6920
+
6921
+ ```ruby
6922
+ require 'glimmer-dsl-libui'
6923
+
6924
+ class FormTable
6925
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
6926
+
6927
+ include Glimmer
6928
+
6929
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
6930
+
6931
+ def initialize
6932
+ @contacts = [
6933
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
6934
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
6935
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
6936
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
6937
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
6938
+ ]
6939
+ end
6940
+
6941
+ def launch
6942
+ window('Contacts', 600, 600) { |w|
6943
+ margined true
6944
+
6945
+ vertical_box {
6946
+ form {
6947
+ stretchy false
6948
+
6949
+ entry {
6950
+ label 'Name'
6951
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6952
+ }
6953
+
6954
+ entry {
6955
+ label 'Email'
6956
+ text <=> [self, :email]
6957
+ }
6958
+
6959
+ entry {
6960
+ label 'Phone'
6961
+ text <=> [self, :phone]
6962
+ }
6963
+
6964
+ entry {
6965
+ label 'City'
6966
+ text <=> [self, :city]
6967
+ }
6968
+
6969
+ entry {
6970
+ label 'State'
6971
+ text <=> [self, :state]
6972
+ }
6973
+ }
6974
+
6975
+ button('Save Contact') {
6976
+ stretchy false
6977
+
6978
+ on_clicked do
6979
+ new_row = [name, email, phone, city, state]
6980
+ if new_row.include?('')
6981
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6982
+ else
6983
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to implicit data-binding
6984
+ @unfiltered_contacts = @contacts.dup
6985
+ self.name = '' # automatically clears name entry through explicit data-binding
6986
+ self.email = ''
6987
+ self.phone = ''
6988
+ self.city = ''
6989
+ self.state = ''
6990
+ end
6991
+ end
6992
+ }
6993
+
6994
+ search_entry {
6995
+ stretchy false
6996
+ # bidirectional data-binding of text to self.filter_value with after_write option
6997
+ text <=> [self, :filter_value,
6998
+ after_write: ->(filter_value) { # execute after write to self.filter_value
6999
+ @unfiltered_contacts ||= @contacts.dup
7000
+ # Unfilter first to remove any previous filters
7001
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
7002
+ # Now, apply filter if entered
7003
+ unless filter_value.empty?
7004
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
7005
+ contact.members.any? do |attribute|
7006
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
7007
+ end
7008
+ end
7009
+ end
7010
+ }
7011
+ ]
7012
+ }
7013
+
7014
+ table {
7015
+ text_column('Name')
7016
+ text_column('Email')
7017
+ text_column('Phone')
7018
+ text_column('City')
7019
+ text_column('State/Province')
7020
+
7021
+ editable true
7022
+ cell_rows <=> [self, :contacts, column_attributes: {'State/Province' => :state}] # explicit data-binding to Model Array with column_attributes mapping for a specific column
7023
+
7024
+ on_changed do |row, type, row_data|
7025
+ puts "Row #{row} #{type}: #{row_data}"
7026
+ end
7027
+ }
7028
+ }
7029
+ }.show
7030
+ end
7031
+ end
7032
+
7033
+ FormTable.new.launch
7034
+ ```
7035
+
7036
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
7037
+
7038
+ ```ruby
7039
+
7040
+ require 'glimmer-dsl-libui'
7041
+
7042
+ class FormTable
7043
+ Contact = Struct.new(:full_name, :email_address, :phone_number, :city_or_town, :state_or_province)
7044
+
7045
+ include Glimmer
7046
+
7047
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
7048
+
7049
+ def initialize
7050
+ @contacts = [
7051
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
7052
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
7053
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
7054
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
7055
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
7056
+ ]
7057
+ end
7058
+
7059
+ def launch
7060
+ window('Contacts', 600, 600) { |w|
7061
+ margined true
7062
+
7063
+ vertical_box {
7064
+ form {
7065
+ stretchy false
7066
+
7067
+ entry {
7068
+ label 'Name'
7069
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
7070
+ }
7071
+
7072
+ entry {
7073
+ label 'Email'
7074
+ text <=> [self, :email]
7075
+ }
7076
+
7077
+ entry {
7078
+ label 'Phone'
7079
+ text <=> [self, :phone]
7080
+ }
7081
+
7082
+ entry {
7083
+ label 'City'
7084
+ text <=> [self, :city]
7085
+ }
7086
+
7087
+ entry {
7088
+ label 'State'
7089
+ text <=> [self, :state]
7090
+ }
7091
+ }
7092
+
7093
+ button('Save Contact') {
7094
+ stretchy false
7095
+
7096
+ on_clicked do
7097
+ new_row = [name, email, phone, city, state]
7098
+ if new_row.include?('')
7099
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
7100
+ else
7101
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to implicit data-binding
7102
+ @unfiltered_contacts = @contacts.dup
7103
+ self.name = '' # automatically clears name entry through explicit data-binding
7104
+ self.email = ''
7105
+ self.phone = ''
7106
+ self.city = ''
7107
+ self.state = ''
7108
+ end
7109
+ end
7110
+ }
7111
+
7112
+ search_entry {
7113
+ stretchy false
7114
+ # bidirectional data-binding of text to self.filter_value with after_write option
7115
+ text <=> [self, :filter_value,
7116
+ after_write: ->(filter_value) { # execute after write to self.filter_value
7117
+ @unfiltered_contacts ||= @contacts.dup
7118
+ # Unfilter first to remove any previous filters
7119
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
7120
+ # Now, apply filter if entered
7121
+ unless filter_value.empty?
7122
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
7123
+ contact.members.any? do |attribute|
7124
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
7125
+ end
7126
+ end
7127
+ end
7128
+ }
7129
+ ]
7130
+ }
7131
+
7132
+ table {
7133
+ text_column('Name')
7134
+ text_column('Email')
7135
+ text_column('Phone')
7136
+ text_column('City')
7137
+ text_column('State')
7138
+
7139
+ editable true
7140
+ 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
7141
+
7142
+ on_changed do |row, type, row_data|
7143
+ puts "Row #{row} #{type}: #{row_data}"
7144
+ end
7145
+ }
7146
+ }
7147
+ }.show
7148
+ end
7149
+ end
7150
+
7151
+ FormTable.new.launch
7152
+ ```
7153
+
7154
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (with explicit [data-binding](#data-binding) to raw data):
6306
7155
 
6307
7156
  ```ruby
6308
7157
  require 'glimmer-dsl-libui'
@@ -6332,7 +7181,7 @@ class FormTable
6332
7181
 
6333
7182
  entry {
6334
7183
  label 'Name'
6335
- text <=> [self, :name]
7184
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6336
7185
  }
6337
7186
 
6338
7187
  entry {
@@ -6364,8 +7213,8 @@ class FormTable
6364
7213
  if new_row.include?('')
6365
7214
  msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6366
7215
  else
6367
- @data << new_row # automatically inserts a row into the table due to implicit data-binding
6368
- @unfiltered_data = @data.dup
7216
+ data << new_row # automatically inserts a row into the table due to implicit data-binding
7217
+ @unfiltered_data = data.dup
6369
7218
  self.name = '' # automatically clears name entry through explicit data-binding
6370
7219
  self.email = ''
6371
7220
  self.phone = ''
@@ -6377,14 +7226,15 @@ class FormTable
6377
7226
 
6378
7227
  search_entry {
6379
7228
  stretchy false
6380
- text <=> [self, :filter_value, # bidirectional data-binding of text to self.filter_value with after_write option
7229
+ # bidirectional data-binding of text to self.filter_value with after_write option
7230
+ text <=> [self, :filter_value,
6381
7231
  after_write: ->(filter_value) { # execute after write to self.filter_value
6382
- @unfiltered_data ||= @data.dup
7232
+ @unfiltered_data ||= data.dup
6383
7233
  # Unfilter first to remove any previous filters
6384
- self.data = @unfiltered_data # affects table indirectly through explicit data-binding
7234
+ data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
6385
7235
  # Now, apply filter if entered
6386
7236
  unless filter_value.empty?
6387
- self.data = @data.filter do |row_data| # affects table indirectly through explicit data-binding
7237
+ data.filter! do |row_data| # affects table indirectly through implicit data-binding
6388
7238
  row_data.any? do |cell|
6389
7239
  cell.to_s.downcase.include?(filter_value.downcase)
6390
7240
  end
@@ -6400,8 +7250,9 @@ class FormTable
6400
7250
  text_column('Phone')
6401
7251
  text_column('City')
6402
7252
  text_column('State')
6403
-
6404
- cell_rows <=> [self, :data] # explicit data-binding
7253
+
7254
+ editable true
7255
+ cell_rows <=> [self, :data] # explicit data-binding to raw data Array of Arrays
6405
7256
 
6406
7257
  on_changed do |row, type, row_data|
6407
7258
  puts "Row #{row} #{type}: #{row_data}"
@@ -6415,7 +7266,7 @@ end
6415
7266
  FormTable.new.launch
6416
7267
  ```
6417
7268
 
6418
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
7269
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 5 (with implicit [data-binding](#data-binding)):
6419
7270
 
6420
7271
  ```ruby
6421
7272
  require 'glimmer-dsl-libui'
@@ -6503,7 +7354,8 @@ window('Contacts', 600, 600) { |w|
6503
7354
  text_column('City')
6504
7355
  text_column('State')
6505
7356
 
6506
- cell_rows data # implicit data-binding
7357
+ editable true
7358
+ cell_rows data # implicit data-binding to raw data Array of Arrays
6507
7359
 
6508
7360
  on_changed do |row, type, row_data|
6509
7361
  puts "Row #{row} #{type}: #{row_data}"