glimmer-dsl-libui 0.4.8 → 0.4.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/README.md +1361 -461
  4. data/VERSION +1 -1
  5. data/examples/basic_table_button.rb +54 -30
  6. data/examples/basic_table_button2.rb +34 -0
  7. data/examples/basic_table_color.rb +104 -26
  8. data/examples/basic_table_color2.rb +2 -14
  9. data/examples/basic_table_color3.rb +37 -0
  10. data/examples/basic_table_image.rb +1 -1
  11. data/examples/basic_table_image2.rb +2 -14
  12. data/examples/basic_table_image3.rb +44 -0
  13. data/examples/basic_table_image_text.rb +1 -2
  14. data/examples/basic_table_image_text2.rb +2 -13
  15. data/examples/basic_table_image_text3.rb +44 -0
  16. data/examples/cpu_percentage.rb +36 -0
  17. data/examples/editable_table.rb +1 -1
  18. data/examples/form_table.rb +21 -17
  19. data/examples/form_table2.rb +104 -85
  20. data/examples/form_table3.rb +113 -0
  21. data/examples/form_table4.rb +110 -0
  22. data/examples/form_table5.rb +94 -0
  23. data/examples/meta_example.rb +6 -4
  24. data/examples/snake.rb +19 -10
  25. data/examples/snake2.rb +97 -0
  26. data/examples/tic_tac_toe.rb +1 -0
  27. data/examples/tic_tac_toe2.rb +84 -0
  28. data/glimmer-dsl-libui.gemspec +0 -0
  29. data/lib/glimmer/dsl/libui/control_expression.rb +2 -1
  30. data/lib/glimmer/dsl/libui/shape_expression.rb +2 -2
  31. data/lib/glimmer/dsl/libui/string_expression.rb +2 -1
  32. data/lib/glimmer/libui/attributed_string.rb +3 -2
  33. data/lib/glimmer/libui/control_proxy/checkbox_proxy.rb +1 -2
  34. data/lib/glimmer/libui/control_proxy/color_button_proxy.rb +1 -2
  35. data/lib/glimmer/libui/control_proxy/column/background_color_column_proxy.rb +4 -0
  36. data/lib/glimmer/libui/control_proxy/combobox_proxy.rb +1 -2
  37. data/lib/glimmer/libui/control_proxy/date_time_picker_proxy.rb +1 -2
  38. data/lib/glimmer/libui/control_proxy/editable_combobox_proxy.rb +1 -2
  39. data/lib/glimmer/libui/control_proxy/entry_proxy.rb +1 -2
  40. data/lib/glimmer/libui/control_proxy/font_button_proxy.rb +5 -1
  41. data/lib/glimmer/libui/control_proxy/menu_item_proxy/check_menu_item_proxy.rb +1 -2
  42. data/lib/glimmer/libui/control_proxy/menu_item_proxy/radio_menu_item_proxy.rb +1 -2
  43. data/lib/glimmer/libui/control_proxy/multiline_entry_proxy.rb +1 -2
  44. data/lib/glimmer/libui/control_proxy/radio_buttons_proxy.rb +1 -2
  45. data/lib/glimmer/libui/control_proxy/slider_proxy.rb +1 -2
  46. data/lib/glimmer/libui/control_proxy/spinbox_proxy.rb +1 -2
  47. data/lib/glimmer/libui/control_proxy/table_proxy.rb +95 -29
  48. data/lib/glimmer/libui/control_proxy.rb +4 -2
  49. data/lib/glimmer/libui/data_bindable.rb +34 -4
  50. data/lib/glimmer/libui/shape.rb +3 -2
  51. data/lib/glimmer/libui.rb +2 -2
  52. data/lib/glimmer-dsl-libui.rb +1 -0
  53. metadata +12 -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.8
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.12
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
@@ -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)
@@ -267,6 +393,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
267
393
  - [Button Counter](#button-counter)
268
394
  - [Color The Circles](#color-the-circles)
269
395
  - [Control Gallery](#control-gallery)
396
+ - [CPU Percentage](#cpu-percentage)
270
397
  - [Custom Draw Text](#custom-draw-text)
271
398
  - [Dynamic Area](#dynamic-area)
272
399
  - [Editable Column Table](#editable-column-table)
@@ -373,10 +500,20 @@ gem install glimmer-dsl-libui
373
500
  Or install via Bundler `Gemfile`:
374
501
 
375
502
  ```ruby
376
- gem 'glimmer-dsl-libui', '~> 0.4.8'
503
+ gem 'glimmer-dsl-libui', '~> 0.4.12'
377
504
  ```
378
505
 
379
- 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.
506
+ Test that installation worked by running the [Meta-Example](#examples):
507
+
508
+ ```
509
+ ruby -r glimmer-dsl-libui -e "require 'examples/meta_example'"
510
+ ```
511
+
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.
380
517
 
381
518
  Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
382
519
 
@@ -451,7 +588,7 @@ Keyword(Args) | Properties | Listeners
451
588
  `about_menu_item` | None | `on_clicked`
452
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)`
453
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
454
- `background_color_column(name as String)` | None | None
591
+ `background_color_column` | None | None
455
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
456
593
  `button(text as String)` | `text` (`String`) | `on_clicked`
457
594
  `button_column(name as String)` | `enabled` (Boolean) | None
@@ -615,120 +752,110 @@ Note that the `cell_rows` property declaration results in "implicit data-binding
615
752
  - Inserting cell rows: Calling `Array#<<`, `Array#push`, `Array#prepend`, or any insertion/addition `Array` method automatically inserts rows in actual `table` control
616
753
  - Changing cell rows: Calling `Array#[]=`, `Array#map!`, or any update `Array` method automatically updates rows in actual `table` control
617
754
 
755
+ ([explicit data-binding](#data-binding) supports everything available with implicit data-binding too)
756
+
618
757
  Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
619
758
 
620
759
  ```ruby
621
760
  require 'glimmer-dsl-libui'
622
761
 
623
- class FormTable
624
- include Glimmer
625
-
626
- attr_accessor :name, :email, :phone, :city, :state, :filter_value
627
-
628
- def initialize
629
- @data = [
630
- ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
631
- ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
632
- ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
633
- ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
634
- ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
635
- ]
636
- end
762
+ include Glimmer
763
+
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|
773
+ margined true
637
774
 
638
- def launch
639
- window('Contacts', 600, 600) { |w|
640
- margined true
775
+ vertical_box {
776
+ form {
777
+ stretchy false
641
778
 
642
- vertical_box {
643
- form {
644
- stretchy false
645
-
646
- entry {
647
- label 'Name'
648
- text <=> [self, :name]
649
- }
650
-
651
- entry {
652
- label 'Email'
653
- text <=> [self, :email]
654
- }
655
-
656
- entry {
657
- label 'Phone'
658
- text <=> [self, :phone]
659
- }
660
-
661
- entry {
662
- label 'City'
663
- text <=> [self, :city]
664
- }
665
-
666
- entry {
667
- label 'State'
668
- text <=> [self, :state]
669
- }
670
- }
671
-
672
- button('Save Contact') {
673
- stretchy false
674
-
675
- on_clicked do
676
- new_row = [name, email, phone, city, state]
677
- if new_row.include?('')
678
- msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
679
- else
680
- @data << new_row # automatically inserts a row into the table due to implicit data-binding
681
- @unfiltered_data = @data.dup
682
- self.name = '' # automatically clears name entry through explicit data-binding
683
- self.email = ''
684
- self.phone = ''
685
- self.city = ''
686
- self.state = ''
779
+ @name_entry = entry {
780
+ label 'Name'
781
+ }
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)
687
832
  end
688
833
  end
689
- }
690
-
691
- search_entry {
692
- stretchy false
693
- text <=> [self, :filter_value, # bidirectional data-binding of text to self.filter_value with after_write option
694
- after_write: ->(filter_value) { # execute after write to self.filter_value
695
- @unfiltered_data ||= @data.dup
696
- # Unfilter first to remove any previous filters
697
- @data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
698
- # Now, apply filter if entered
699
- unless filter_value.empty?
700
- @data.filter! do |row_data| # affects table indirectly through implicit data-binding
701
- row_data.any? do |cell|
702
- cell.to_s.downcase.include?(filter_value.downcase)
703
- end
704
- end
705
- end
706
- }
707
- ]
708
- }
709
-
710
- table {
711
- text_column('Name')
712
- text_column('Email')
713
- text_column('Phone')
714
- text_column('City')
715
- text_column('State')
834
+ end
835
+ end
836
+ }
716
837
 
717
- cell_rows @data # implicit data-binding
718
-
719
- on_changed do |row, type, row_data|
720
- puts "Row #{row} #{type}: #{row_data}"
721
- end
722
- }
723
- }
724
- }.show
725
- end
726
- end
838
+ table {
839
+ text_column('Name')
840
+ text_column('Email')
841
+ text_column('Phone')
842
+ text_column('City')
843
+ text_column('State')
727
844
 
728
- FormTable.new.launch
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
729
854
  ```
730
855
 
731
- ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png)
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)
732
859
 
733
860
  Learn more by checking out [examples](#examples).
734
861
 
@@ -798,9 +925,9 @@ Check [examples/dynamic_area.rb](#dynamic-area) for a more detailed semi-declara
798
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.
799
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.
800
927
 
801
- Mac |Linux
802
- ----|-----
803
- ![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)
804
931
 
805
932
  Check [examples/basic_scrolling_area.rb](#basic-scrolling-area) for a more detailed example.
806
933
 
@@ -1216,6 +1343,7 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t
1216
1343
  - When destroying a control nested under a `horizontal_box` or `vertical_box`, it is automatically deleted from the box's children
1217
1344
  - When destroying a control nested under a `form`, it is automatically deleted from the form's children
1218
1345
  - When destroying a control nested under a `window` or `group`, it is automatically unset as their child to allow successful destruction
1346
+ - When destroying a control that has a data-binding to a model attribute, the data-binding observer registration is automatically deregistered
1219
1347
  - 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`
1220
1348
  - Smart defaults for `grid` child properties are `left` (`0`), `top` (`0`), `xspan` (`1`), `yspan` (`1`), `hexpand` (`false`), `halign` (`:fill`), `vexpand` (`false`), and `valign` (`:fill`)
1221
1349
  - 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`)
@@ -1381,6 +1509,8 @@ Data-binding supports utilizing the [MVP (Model View Presenter)](https://en.wiki
1381
1509
 
1382
1510
  ![MVP](https://www.researchgate.net/profile/Gilles-Perrouin/publication/320249584/figure/fig8/AS:668260987068418@1536337243385/Model-view-presenter-architecture.png)
1383
1511
 
1512
+ #### Bidirectional (Two-Way) Data-Binding
1513
+
1384
1514
  [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):
1385
1515
  - `checkbox`: `checked`
1386
1516
  - `check_menu_item`: `checked`
@@ -1398,27 +1528,184 @@ Data-binding supports utilizing the [MVP (Model View Presenter)](https://en.wiki
1398
1528
  - `search_entry`: `text`
1399
1529
  - `slider`: `value`
1400
1530
  - `spinbox`: `value`
1531
+ - `table`: `cell_rows` (explicit data-binding by using `<=>` and [implicit data-binding](#table-api) by assigning value directly)
1401
1532
  - `time_picker`: `time`
1402
1533
 
1403
- Example of bidirectional data-binding:
1534
+ Example of bidirectional data-binding:
1535
+
1536
+ ```ruby
1537
+ entry {
1538
+ text <=> [contract, :legal_text]
1539
+ }
1540
+ ```
1541
+
1542
+ That is data-binding a contract's legal text to an `entry` `text` property.
1543
+
1544
+ Another example of bidirectional data-binding with an option:
1545
+
1546
+ ```ruby
1547
+ entry {
1548
+ text <=> [self, :entered_text, after_write: ->(text) {puts text}]
1549
+ }
1550
+ ```
1551
+
1552
+ That is data-binding `entered_text` attribute on `self` to `entry` `text` property and printing text after write to the model.
1553
+
1554
+ ##### Table Data-Binding
1555
+
1556
+ One note about `table` `cell_rows` data-binding is that it works with either:
1557
+ - Raw data `Array` (rows) of `Array`s (column cells)
1558
+ - 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.
1559
+
1560
+ 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)):
1561
+
1562
+ ```ruby
1563
+ require 'glimmer-dsl-libui'
1564
+
1565
+ include Glimmer
1566
+
1567
+ data = [
1568
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],
1569
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],
1570
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],
1571
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],
1572
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
1573
+ ]
1574
+
1575
+ window('Contacts', 600, 600) {
1576
+ table {
1577
+ text_column('Name')
1578
+ text_column('Email')
1579
+ text_column('Phone')
1580
+ text_column('City')
1581
+ text_column('State')
1582
+
1583
+ cell_rows data
1584
+ }
1585
+ }.show
1586
+ ```
1587
+
1588
+ Example of `table` explicit data-binding of `cell_rows` to Model `Array` (you may copy/paste in [`girb`](#girb-glimmer-irb)):
1589
+
1590
+ ```ruby
1591
+ require 'glimmer-dsl-libui'
1592
+
1593
+ class SomeTable
1594
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
1595
+
1596
+ include Glimmer
1597
+
1598
+ attr_accessor :contacts
1599
+
1600
+ def initialize
1601
+ @contacts = [
1602
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
1603
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
1604
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
1605
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
1606
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
1607
+ ]
1608
+ end
1609
+
1610
+ def launch
1611
+ window('Contacts', 600, 200) {
1612
+ table {
1613
+ text_column('Name')
1614
+ text_column('Email')
1615
+ text_column('Phone')
1616
+ text_column('City')
1617
+ text_column('State')
1618
+
1619
+ cell_rows <=> [self, :contacts] # explicit data-binding to Model Array auto-inferring model attribute names from underscored table column names by convention
1620
+ }
1621
+ }.show
1622
+ end
1623
+ end
1624
+
1625
+ SomeTable.new.launch
1626
+ ```
1627
+
1628
+ 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)):
1404
1629
 
1405
1630
  ```ruby
1406
- entry {
1407
- text <=> [contract, :legal_text]
1408
- }
1409
- ```
1631
+ require 'glimmer-dsl-libui'
1410
1632
 
1411
- That is data-binding a contract's legal text to an `entry` `text` property.
1633
+ class SomeTable
1634
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
1635
+
1636
+ include Glimmer
1637
+
1638
+ attr_accessor :contacts
1639
+
1640
+ def initialize
1641
+ @contacts = [
1642
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
1643
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
1644
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
1645
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
1646
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
1647
+ ]
1648
+ end
1649
+
1650
+ def launch
1651
+ window('Contacts', 600, 200) {
1652
+ table {
1653
+ text_column('Name')
1654
+ text_column('Email')
1655
+ text_column('Phone')
1656
+ text_column('City/Town')
1657
+ text_column('State/Province')
1658
+
1659
+ cell_rows <=> [self, :contacts, column_attributes: {'City/Town' => :city, 'State/Province' => :state}]
1660
+ }
1661
+ }.show
1662
+ end
1663
+ end
1412
1664
 
1413
- Another example of bidirectional data-binding with an option:
1665
+ SomeTable.new.launch
1666
+ ```
1667
+
1668
+ 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)):
1414
1669
 
1415
1670
  ```ruby
1416
- entry {
1417
- text <=> [self, :entered_text, after_write: ->(text) {puts text}]
1418
- }
1671
+ require 'glimmer-dsl-libui'
1672
+
1673
+ class SomeTable
1674
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
1675
+
1676
+ include Glimmer
1677
+
1678
+ attr_accessor :contacts
1679
+
1680
+ def initialize
1681
+ @contacts = [
1682
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
1683
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
1684
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
1685
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
1686
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
1687
+ ]
1688
+ end
1689
+
1690
+ def launch
1691
+ window('Contacts', 600, 200) {
1692
+ table {
1693
+ text_column('Full Name')
1694
+ text_column('Email Address')
1695
+ text_column('Phone Number')
1696
+ text_column('City or Town')
1697
+ text_column('State or Province')
1698
+
1699
+ cell_rows <=> [self, :contacts, column_attributes: [:name, :email, :phone, :city, :state]]
1700
+ }
1701
+ }.show
1702
+ end
1703
+ end
1704
+
1705
+ SomeTable.new.launch
1419
1706
  ```
1420
1707
 
1421
- That is data-binding `entered_text` attribute on `self` to `entry` `text` property and printing text after write to the model.
1708
+ #### Unidirectional (One-Way) Data-Binding
1422
1709
 
1423
1710
  [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).
1424
1711
 
@@ -1442,6 +1729,8 @@ window {
1442
1729
 
1443
1730
  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`.
1444
1731
 
1732
+ #### Data-Binding API
1733
+
1445
1734
  To summarize the data-binding API:
1446
1735
  - `view_property <=> [model, attribute, *read_or_write_options]`: Bidirectional (two-way) data-binding to Model attribute accessor
1447
1736
  - `view_property <= [model, attribute, *read_only_options]`: Unidirectional (one-way) data-binding to Model attribute reader
@@ -1472,12 +1761,13 @@ entry {
1472
1761
  }
1473
1762
  ```
1474
1763
 
1475
- Data-binding gotchas:
1764
+ 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.
1765
+
1766
+ #### Data-Binding Gotchas
1767
+
1476
1768
  - 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)
1477
1769
  - 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.
1478
1770
 
1479
- 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.
1480
-
1481
1771
  ### API Gotchas
1482
1772
 
1483
1773
  - There is no proper way to destroy `grid` children due to [libui](https://github.com/andlabs/libui) not offering any API for deleting them from `grid` (no `grid_delete` similar to `box_delete` for `horizontal_box` and `vertical_box`).
@@ -1489,272 +1779,12 @@ Learn more from data-binding usage in [Login](#login) (4 data-binding versions),
1489
1779
  - `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.
1490
1780
  - 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.
1491
1781
  - As per the code of [examples/basic_transform.rb](#basic-transform), Windows requires different ordering of transforms than Mac and Linux.
1782
+ - `scrolling_area#scroll_to` does not seem to work on Windows and Linux, but works fine on Mac
1492
1783
 
1493
1784
  ### Original API
1494
1785
 
1495
1786
  Here are all the lower-level [LibUI](https://github.com/kojix2/LibUI) API methods utilized by [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui):
1496
- - `alloc_control`
1497
- - `area_begin_user_window_move`
1498
- - `area_begin_user_window_resize`
1499
- - `area_queue_redraw_all`
1500
- - `area_scroll_to`
1501
- - `area_set_size`
1502
- - `attribute_color`
1503
- - `attribute_family`
1504
- - `attribute_features`
1505
- - `attribute_get_type`
1506
- - `attribute_italic`
1507
- - `attribute_size`
1508
- - `attribute_stretch`
1509
- - `attribute_underline`
1510
- - `attribute_underline_color`
1511
- - `attribute_weight`
1512
- - `attributed_string_append_unattributed`
1513
- - `attributed_string_byte_index_to_grapheme`
1514
- - `attributed_string_delete`
1515
- - `attributed_string_for_each_attribute`
1516
- - `attributed_string_grapheme_to_byte_index`
1517
- - `attributed_string_insert_at_unattributed`
1518
- - `attributed_string_len`
1519
- - `attributed_string_num_graphemes`
1520
- - `attributed_string_set_attribute`
1521
- - `attributed_string_string`
1522
- - `box_append`
1523
- - `box_delete`
1524
- - `box_padded`
1525
- - `box_set_padded`
1526
- - `button_on_clicked`
1527
- - `button_set_text`
1528
- - `button_text`
1529
- - `checkbox_checked`
1530
- - `checkbox_on_toggled`
1531
- - `checkbox_set_checked`
1532
- - `checkbox_set_text`
1533
- - `checkbox_text`
1534
- - `color_button_color`
1535
- - `color_button_on_changed`
1536
- - `color_button_set_color`
1537
- - `combobox_append`
1538
- - `combobox_on_selected`
1539
- - `combobox_selected`
1540
- - `combobox_set_selected`
1541
- - `control_destroy`
1542
- - `control_disable`
1543
- - `control_enable`
1544
- - `control_enabled`
1545
- - `control_enabled_to_user`
1546
- - `control_handle`
1547
- - `control_hide`
1548
- - `control_parent`
1549
- - `control_set_parent`
1550
- - `control_show`
1551
- - `control_toplevel`
1552
- - `control_verify_set_parent`
1553
- - `control_visible`
1554
- - `date_time_picker_on_changed`
1555
- - `date_time_picker_set_time`
1556
- - `date_time_picker_time`
1557
- - `draw_clip`
1558
- - `draw_fill`
1559
- - `draw_free_path`
1560
- - `draw_free_text_layout`
1561
- - `draw_matrix_invert`
1562
- - `draw_matrix_invertible`
1563
- - `draw_matrix_multiply`
1564
- - `draw_matrix_rotate`
1565
- - `draw_matrix_scale`
1566
- - `draw_matrix_set_identity`
1567
- - `draw_matrix_skew`
1568
- - `draw_matrix_transform_point`
1569
- - `draw_matrix_transform_size`
1570
- - `draw_matrix_translate`
1571
- - `draw_new_path`
1572
- - `draw_new_text_layout`
1573
- - `draw_path_add_rectangle`
1574
- - `draw_path_arc_to`
1575
- - `draw_path_bezier_to`
1576
- - `draw_path_close_figure`
1577
- - `draw_path_end`
1578
- - `draw_path_line_to`
1579
- - `draw_path_new_figure`
1580
- - `draw_path_new_figure_with_arc`
1581
- - `draw_restore`
1582
- - `draw_save`
1583
- - `draw_stroke`
1584
- - `draw_text`
1585
- - `draw_text_layout_extents`
1586
- - `draw_transform`
1587
- - `editable_combobox_append`
1588
- - `editable_combobox_on_changed`
1589
- - `editable_combobox_set_text`
1590
- - `editable_combobox_text`
1591
- - `entry_on_changed`
1592
- - `entry_read_only`
1593
- - `entry_set_read_only`
1594
- - `entry_set_text`
1595
- - `entry_text`
1596
- - `ffi_lib`
1597
- - `ffi_lib=`
1598
- - `font_button_font`
1599
- - `font_button_on_changed`
1600
- - `form_append`
1601
- - `form_delete`
1602
- - `form_padded`
1603
- - `form_set_padded`
1604
- - `free_attribute`
1605
- - `free_attributed_string`
1606
- - `free_control`
1607
- - `free_font_button_font`
1608
- - `free_image`
1609
- - `free_init_error`
1610
- - `free_open_type_features`
1611
- - `free_table_model`
1612
- - `free_table_value`
1613
- - `free_text`
1614
- - `grid_append`
1615
- - `grid_insert_at`
1616
- - `grid_padded`
1617
- - `grid_set_padded`
1618
- - `group_margined`
1619
- - `group_set_child`
1620
- - `group_set_margined`
1621
- - `group_set_title`
1622
- - `group_title`
1623
- - `image_append`
1624
- - `init`
1625
- - `label_set_text`
1626
- - `label_text`
1627
- - `main`
1628
- - `main_step`
1629
- - `main_steps`
1630
- - `menu_append_about_item`
1631
- - `menu_append_check_item`
1632
- - `menu_append_item`
1633
- - `menu_append_preferences_item`
1634
- - `menu_append_quit_item`
1635
- - `menu_append_separator`
1636
- - `menu_item_checked`
1637
- - `menu_item_disable`
1638
- - `menu_item_enable`
1639
- - `menu_item_on_clicked`
1640
- - `menu_item_set_checked`
1641
- - `msg_box`
1642
- - `msg_box_error`
1643
- - `multiline_entry_append`
1644
- - `multiline_entry_on_changed`
1645
- - `multiline_entry_read_only`
1646
- - `multiline_entry_set_read_only`
1647
- - `multiline_entry_set_text`
1648
- - `multiline_entry_text`
1649
- - `new_area`
1650
- - `new_attributed_string`
1651
- - `new_background_attribute`
1652
- - `new_button`
1653
- - `new_checkbox`
1654
- - `new_color_attribute`
1655
- - `new_color_button`
1656
- - `new_combobox`
1657
- - `new_date_picker`
1658
- - `new_date_time_picker`
1659
- - `new_editable_combobox`
1660
- - `new_entry`
1661
- - `new_family_attribute`
1662
- - `new_features_attribute`
1663
- - `new_font_button`
1664
- - `new_form`
1665
- - `new_grid`
1666
- - `new_group`
1667
- - `new_horizontal_box`
1668
- - `new_horizontal_separator`
1669
- - `new_image`
1670
- - `new_italic_attribute`
1671
- - `new_label`
1672
- - `new_menu`
1673
- - `new_multiline_entry`
1674
- - `new_non_wrapping_multiline_entry`
1675
- - `new_open_type_features`
1676
- - `new_password_entry`
1677
- - `new_progress_bar`
1678
- - `new_radio_buttons`
1679
- - `new_scrolling_area`
1680
- - `new_search_entry`
1681
- - `new_size_attribute`
1682
- - `new_slider`
1683
- - `new_spinbox`
1684
- - `new_stretch_attribute`
1685
- - `new_tab`
1686
- - `new_table`
1687
- - `new_table_model`
1688
- - `new_table_value_color`
1689
- - `new_table_value_image`
1690
- - `new_table_value_int`
1691
- - `new_table_value_string`
1692
- - `new_time_picker`
1693
- - `new_underline_attribute`
1694
- - `new_underline_color_attribute`
1695
- - `new_vertical_box`
1696
- - `new_vertical_separator`
1697
- - `new_weight_attribute`
1698
- - `new_window`
1699
- - `on_should_quit`
1700
- - `open_file`
1701
- - `open_type_features_add`
1702
- - `open_type_features_clone`
1703
- - `open_type_features_for_each`
1704
- - `open_type_features_get`
1705
- - `open_type_features_remove`
1706
- - `progress_bar_set_value`
1707
- - `progress_bar_value`
1708
- - `queue_main`
1709
- - `quit`
1710
- - `radio_buttons_append`
1711
- - `radio_buttons_on_selected`
1712
- - `radio_buttons_selected`
1713
- - `radio_buttons_set_selected`
1714
- - `save_file`
1715
- - `slider_on_changed`
1716
- - `slider_set_value`
1717
- - `slider_value`
1718
- - `spinbox_on_changed`
1719
- - `spinbox_set_value`
1720
- - `spinbox_value`
1721
- - `tab_append`
1722
- - `tab_delete`
1723
- - `tab_insert_at`
1724
- - `tab_margined`
1725
- - `tab_num_pages`
1726
- - `tab_set_margined`
1727
- - `table_append_button_column`
1728
- - `table_append_checkbox_column`
1729
- - `table_append_checkbox_text_column`
1730
- - `table_append_image_column`
1731
- - `table_append_image_text_column`
1732
- - `table_append_progress_bar_column`
1733
- - `table_append_text_column`
1734
- - `table_model_row_changed`
1735
- - `table_model_row_deleted`
1736
- - `table_model_row_inserted`
1737
- - `table_value_color`
1738
- - `table_value_get_type`
1739
- - `table_value_image`
1740
- - `table_value_int`
1741
- - `table_value_string`
1742
- - `timer`
1743
- - `uninit`
1744
- - `user_bug_cannot_set_parent_on_toplevel`
1745
- - `window_borderless`
1746
- - `window_content_size`
1747
- - `window_fullscreen`
1748
- - `window_margined`
1749
- - `window_on_closing`
1750
- - `window_on_content_size_changed`
1751
- - `window_set_borderless`
1752
- - `window_set_child`
1753
- - `window_set_content_size`
1754
- - `window_set_fullscreen`
1755
- - `window_set_margined`
1756
- - `window_set_title`
1757
- - `window_title`
1787
+ `alloc_control`, `append_features`, `area_begin_user_window_move`, `area_begin_user_window_resize`, `area_queue_redraw_all`, `area_scroll_to`, `area_set_size`, `attribute_color`, `attribute_family`, `attribute_features`, `attribute_get_type`, `attribute_italic`, `attribute_size`, `attribute_stretch`, `attribute_underline`, `attribute_underline_color`, `attribute_weight`, `attributed_string_append_unattributed`, `attributed_string_byte_index_to_grapheme`, `attributed_string_delete`, `attributed_string_for_each_attribute`, `attributed_string_grapheme_to_byte_index`, `attributed_string_insert_at_unattributed`, `attributed_string_len`, `attributed_string_num_graphemes`, `attributed_string_set_attribute`, `attributed_string_string`, `box_append`, `box_delete`, `box_padded`, `box_set_padded`, `button_on_clicked`, `button_set_text`, `button_text`, `checkbox_checked`, `checkbox_on_toggled`, `checkbox_set_checked`, `checkbox_set_text`, `checkbox_text`, `color_button_color`, `color_button_on_changed`, `color_button_set_color`, `combobox_append`, `combobox_on_selected`, `combobox_selected`, `combobox_set_selected`, `control_destroy`, `control_disable`, `control_enable`, `control_enabled`, `control_enabled_to_user`, `control_handle`, `control_hide`, `control_parent`, `control_set_parent`, `control_show`, `control_toplevel`, `control_verify_set_parent`, `control_visible`, `date_time_picker_on_changed`, `date_time_picker_set_time`, `date_time_picker_time`, `draw_clip`, `draw_fill`, `draw_free_path`, `draw_free_text_layout`, `draw_matrix_invert`, `draw_matrix_invertible`, `draw_matrix_multiply`, `draw_matrix_rotate`, `draw_matrix_scale`, `draw_matrix_set_identity`, `draw_matrix_skew`, `draw_matrix_transform_point`, `draw_matrix_transform_size`, `draw_matrix_translate`, `draw_new_path`, `draw_new_text_layout`, `draw_path_add_rectangle`, `draw_path_arc_to`, `draw_path_bezier_to`, `draw_path_close_figure`, `draw_path_end`, `draw_path_line_to`, `draw_path_new_figure`, `draw_path_new_figure_with_arc`, `draw_restore`, `draw_save`, `draw_stroke`, `draw_text`, `draw_text_layout_extents`, `draw_transform`, `editable_combobox_append`, `editable_combobox_on_changed`, `editable_combobox_set_text`, `editable_combobox_text`, `entry_on_changed`, `entry_read_only`, `entry_set_read_only`, `entry_set_text`, `entry_text`, `ffi_lib`, `ffi_lib=`, `font_button_font`, `font_button_on_changed`, `form_append`, `form_delete`, `form_padded`, `form_set_padded`, `free_attribute`, `free_attributed_string`, `free_control`, `free_font_button_font`, `free_image`, `free_init_error`, `free_open_type_features`, `free_table_model`, `free_table_value`, `free_text`, `grid_append`, `grid_insert_at`, `grid_padded`, `grid_set_padded`, `group_margined`, `group_set_child`, `group_set_margined`, `group_set_title`, `group_title`, `image_append`, `init`, `label_set_text`, `label_text`, `main`, `main_step`, `main_steps`, `menu_append_about_item`, `menu_append_check_item`, `menu_append_item`, `menu_append_preferences_item`, `menu_append_quit_item`, `menu_append_separator`, `menu_item_checked`, `menu_item_disable`, `menu_item_enable`, `menu_item_on_clicked`, `menu_item_set_checked`, `msg_box`, `msg_box_error`, `multiline_entry_append`, `multiline_entry_on_changed`, `multiline_entry_read_only`, `multiline_entry_set_read_only`, `multiline_entry_set_text`, `multiline_entry_text`, `new_area`, `new_attributed_string`, `new_background_attribute`, `new_button`, `new_checkbox`, `new_color_attribute`, `new_color_button`, `new_combobox`, `new_date_picker`, `new_date_time_picker`, `new_editable_combobox`, `new_entry`, `new_family_attribute`, `new_features_attribute`, `new_font_button`, `new_form`, `new_grid`, `new_group`, `new_horizontal_box`, `new_horizontal_separator`, `new_image`, `new_italic_attribute`, `new_label`, `new_menu`, `new_multiline_entry`, `new_non_wrapping_multiline_entry`, `new_open_type_features`, `new_password_entry`, `new_progress_bar`, `new_radio_buttons`, `new_scrolling_area`, `new_search_entry`, `new_size_attribute`, `new_slider`, `new_spinbox`, `new_stretch_attribute`, `new_tab`, `new_table`, `new_table_model`, `new_table_value_color`, `new_table_value_image`, `new_table_value_int`, `new_table_value_string`, `new_time_picker`, `new_underline_attribute`, `new_underline_color_attribute`, `new_vertical_box`, `new_vertical_separator`, `new_weight_attribute`, `new_window`, `on_should_quit`, `open_file`, `open_type_features_add`, `open_type_features_clone`, `open_type_features_for_each`, `open_type_features_get`, `open_type_features_remove`, `progress_bar_set_value`, `progress_bar_value`, `queue_main`, `quit`, `radio_buttons_append`, `radio_buttons_on_selected`, `radio_buttons_selected`, `radio_buttons_set_selected`, `save_file`, `slider_on_changed`, `slider_set_value`, `slider_value`, `spinbox_on_changed`, `spinbox_set_value`, `spinbox_value`, `tab_append`, `tab_delete`, `tab_insert_at`, `tab_margined`, `tab_num_pages`, `tab_set_margined`, `table_append_button_column`, `table_append_checkbox_column`, `table_append_checkbox_text_column`, `table_append_image_column`, `table_append_image_text_column`, `table_append_progress_bar_column`, `table_append_text_column`, `table_model_row_changed`, `table_model_row_deleted`, `table_model_row_inserted`, `table_value_color`, `table_value_get_type`, `table_value_image`, `table_value_int`, `table_value_string`, `timer`, `uninit`, `user_bug_cannot_set_parent_on_toplevel`, `window_borderless`, `window_content_size`, `window_fullscreen`, `window_margined`, `window_on_closing`, `window_on_content_size_changed`, `window_set_borderless`, `window_set_child`, `window_set_content_size`, `window_set_fullscreen`, `window_set_margined`, `window_set_title`, `window_title`
1758
1788
 
1759
1789
  To learn more about the [LibUI](https://github.com/kojix2/LibUI) API exposed through [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui):
1760
1790
  - Check out [LibUI ffi.rb](https://github.com/kojix2/LibUI/blob/main/lib/libui/ffi.rb)
@@ -3063,7 +3093,44 @@ UI.main
3063
3093
  UI.quit
3064
3094
  ```
3065
3095
 
3066
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3096
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (passing file url as image):
3097
+
3098
+ ```ruby
3099
+ # frozen_string_literal: true
3100
+
3101
+ # NOTE:
3102
+ # This example displays images that can be freely downloaded from the Studio Ghibli website.
3103
+
3104
+ require 'glimmer-dsl-libui'
3105
+
3106
+ include Glimmer
3107
+
3108
+ IMAGE_ROWS = []
3109
+
3110
+ 50.times do |i|
3111
+ url = format('https://www.ghibli.jp/gallery/thumb-redturtle%03d.png', (i + 1))
3112
+ puts "Processing Image: #{url}"; $stdout.flush # for Windows
3113
+ IMAGE_ROWS << [url] # array of one column cell
3114
+ rescue StandardError => e
3115
+ warn url, e.message
3116
+ end
3117
+
3118
+ window('The Red Turtle', 310, 350, false) {
3119
+ horizontal_box {
3120
+ table {
3121
+ image_column('www.ghibli.jp/works/red-turtle')
3122
+
3123
+ cell_rows IMAGE_ROWS
3124
+ }
3125
+ }
3126
+
3127
+ on_closing do
3128
+ puts 'Bye Bye'
3129
+ end
3130
+ }.show
3131
+ ```
3132
+
3133
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (automatic construction of `image`):
3067
3134
 
3068
3135
  ```ruby
3069
3136
  # NOTE:
@@ -3098,7 +3165,7 @@ window('The Red Turtle', 310, 350, false) {
3098
3165
  }.show
3099
3166
  ```
3100
3167
 
3101
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (manual construction of `image` from `image_part`):
3168
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (manual construction of `image` from `image_part`):
3102
3169
 
3103
3170
  ```ruby
3104
3171
  # NOTE:
@@ -3166,7 +3233,44 @@ Mac | Windows | Linux
3166
3233
  ----|---------|------
3167
3234
  ![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)
3168
3235
 
3169
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3236
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (passing file url as image):
3237
+
3238
+ ```ruby
3239
+ # frozen_string_literal: true
3240
+
3241
+ # NOTE:
3242
+ # This example displays images that can be freely downloaded from the Studio Ghibli website.
3243
+
3244
+ require 'glimmer-dsl-libui'
3245
+
3246
+ include Glimmer
3247
+
3248
+ IMAGE_ROWS = []
3249
+
3250
+ 5.times do |i|
3251
+ url = format('https://www.ghibli.jp/gallery/thumb-redturtle%03d.png', (i + 1))
3252
+ puts "Processing Image: #{url}"; $stdout.flush # for Windows
3253
+ text = url.sub('https://www.ghibli.jp/gallery/thumb-redturtle', '').sub('.png', '')
3254
+ IMAGE_ROWS << [[url, text], [url, text]] # cell values are dual-element arrays
3255
+ rescue StandardError => e
3256
+ warn url, e.message
3257
+ end
3258
+
3259
+ window('The Red Turtle', 670, 350) {
3260
+ horizontal_box {
3261
+ table {
3262
+ image_text_column('image/number')
3263
+ image_text_column('image/number (editable)') {
3264
+ editable true
3265
+ }
3266
+
3267
+ cell_rows IMAGE_ROWS
3268
+ }
3269
+ }
3270
+ }.show
3271
+ ```
3272
+
3273
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (automatic construction of `image`):
3170
3274
 
3171
3275
  ```ruby
3172
3276
  # NOTE:
@@ -3268,7 +3372,70 @@ Mac | Windows | Linux
3268
3372
  ----|---------|------
3269
3373
  ![glimmer-dsl-libui-mac-basic-table-button.png](images/glimmer-dsl-libui-mac-basic-table-button.png) ![glimmer-dsl-libui-mac-basic-table-button-deleted.png](images/glimmer-dsl-libui-mac-basic-table-button-deleted.png) | ![glimmer-dsl-libui-windows-basic-table-button.png](images/glimmer-dsl-libui-windows-basic-table-button.png) ![glimmer-dsl-libui-windows-basic-table-button-deleted.png](images/glimmer-dsl-libui-windows-basic-table-button-deleted.png) | ![glimmer-dsl-libui-linux-basic-table-button.png](images/glimmer-dsl-libui-linux-basic-table-button.png) ![glimmer-dsl-libui-linux-basic-table-button-deleted.png](images/glimmer-dsl-libui-linux-basic-table-button-deleted.png)
3270
3374
 
3271
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3375
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
3376
+
3377
+ ```ruby
3378
+ require 'glimmer-dsl-libui'
3379
+
3380
+ class BasicTableButton
3381
+ BasicAnimal = Struct.new(:name, :sound)
3382
+
3383
+ class Animal < BasicAnimal
3384
+ def action
3385
+ 'delete'
3386
+ end
3387
+ end
3388
+
3389
+ include Glimmer
3390
+
3391
+ attr_accessor :animals
3392
+
3393
+ def initialize
3394
+ @animals = [
3395
+ Animal.new('cat', 'meow'),
3396
+ Animal.new('dog', 'woof'),
3397
+ Animal.new('chicken', 'cock-a-doodle-doo'),
3398
+ Animal.new('horse', 'neigh'),
3399
+ Animal.new('cow', 'moo'),
3400
+ ]
3401
+ end
3402
+
3403
+ def launch
3404
+ window('Animal sounds', 400, 200) {
3405
+ horizontal_box {
3406
+ table {
3407
+ text_column('Animal')
3408
+ text_column('Description')
3409
+ button_column('Action') {
3410
+ on_clicked do |row|
3411
+ # Option 1: direct data deletion is the simpler solution
3412
+ # @animals.delete_at(row) # automatically deletes actual table row due to explicit data-binding
3413
+
3414
+ # Option 2: cloning only to demonstrate table row deletion upon explicit setting of animals attribute (cloning is not recommended beyond demonstrating this point)
3415
+ new_animals = @animals.clone
3416
+ new_animals.delete_at(row)
3417
+ self.animals = new_animals # automatically loses deleted table row due to explicit data-binding
3418
+ end
3419
+ }
3420
+
3421
+
3422
+ cell_rows <= [self, :animals, column_attributes: {'Animal' => :name, 'Description' => :sound}]
3423
+
3424
+ # explicit unidirectional data-binding of table cell_rows to self.animals
3425
+ on_changed do |row, type, row_data|
3426
+ puts "Row #{row} #{type}: #{row_data}"
3427
+ $stdout.flush
3428
+ end
3429
+ }
3430
+ }
3431
+ }.show
3432
+ end
3433
+ end
3434
+
3435
+ BasicTableButton.new.launch
3436
+ ```
3437
+
3438
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (with implicit [data-binding](#data-binding)):
3272
3439
 
3273
3440
  ```ruby
3274
3441
  require 'glimmer-dsl-libui'
@@ -3477,16 +3644,126 @@ Mac | Windows | Linux
3477
3644
  ----|---------|------
3478
3645
  ![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)
3479
3646
 
3480
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3647
+ 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):
3481
3648
 
3482
3649
  ```ruby
3483
- # frozen_string_literal: true
3650
+ require 'glimmer-dsl-libui'
3651
+
3652
+ class BasicTableColor
3653
+ Animal = Struct.new(:name, :sound, :mammal)
3654
+
3655
+ class AnimalPresenter < Animal
3656
+ def name_color
3657
+ color = case name
3658
+ when 'cat'
3659
+ :red
3660
+ when 'dog'
3661
+ :yellow
3662
+ when 'chicken'
3663
+ :beige
3664
+ when 'horse'
3665
+ :purple
3666
+ when 'cow'
3667
+ :gray
3668
+ end
3669
+ [name, color]
3670
+ end
3671
+
3672
+ def sound_color
3673
+ color = case name
3674
+ when 'cat', 'chicken', 'cow'
3675
+ :blue
3676
+ when 'dog', 'horse'
3677
+ {r: 240, g: 32, b: 32}
3678
+ end
3679
+ [sound, color]
3680
+ end
3681
+
3682
+ def mammal_description_color
3683
+ color = case name
3684
+ when 'cat', 'dog', 'horse', 'cow'
3685
+ :green
3686
+ when 'chicken'
3687
+ :red
3688
+ end
3689
+ [mammal, 'mammal', color]
3690
+ end
3691
+
3692
+ def image_description_color
3693
+ color = case name
3694
+ when 'cat', 'dog', 'horse'
3695
+ :dark_blue
3696
+ when 'chicken'
3697
+ :beige
3698
+ when 'cow'
3699
+ :brown
3700
+ end
3701
+ [img, 'Glimmer', color]
3702
+ end
3703
+
3704
+ def img
3705
+ # scale image to 24x24 (can be passed as file path String only instead of Array to avoid scaling)
3706
+ [File.expand_path('../icons/glimmer.png', __dir__), 24, 24]
3707
+ end
3708
+
3709
+ def background_color
3710
+ case name
3711
+ when 'cat'
3712
+ {r: 255, g: 120, b: 0, a: 0.5}
3713
+ when 'dog'
3714
+ :skyblue
3715
+ when 'chicken'
3716
+ {r: 5, g: 120, b: 110}
3717
+ when 'horse'
3718
+ '#13a1fb'
3719
+ when 'cow'
3720
+ 0x12ff02
3721
+ end
3722
+ end
3723
+ end
3724
+
3725
+ include Glimmer
3726
+
3727
+ attr_accessor :animals
3728
+
3729
+ def initialize
3730
+ @animals = [
3731
+ AnimalPresenter.new('cat', 'meow', true),
3732
+ AnimalPresenter.new('dog', 'woof', true),
3733
+ AnimalPresenter.new('chicken', 'cock-a-doodle-doo', false),
3734
+ AnimalPresenter.new('horse', 'neigh', true),
3735
+ AnimalPresenter.new('cow', 'moo', true),
3736
+ ]
3737
+ end
3738
+
3739
+ def launch
3740
+ window('Animals', 500, 200) {
3741
+ horizontal_box {
3742
+ table {
3743
+ text_color_column('Animal')
3744
+ text_color_column('Sound')
3745
+ checkbox_text_color_column('Description')
3746
+ image_text_color_column('GUI')
3747
+ background_color_column # must always be the last column and always expects data-binding model attribute `background_color` when binding to Array of models
3748
+
3749
+ cell_rows <= [self, :animals, column_attributes: {'Animal' => :name_color, 'Sound' => :sound_color, 'Description' => :mammal_description_color, 'GUI' => :image_description_color}]
3750
+ }
3751
+ }
3752
+ }.show
3753
+ end
3754
+ end
3755
+
3756
+ BasicTableColor.new.launch
3757
+ ```
3758
+
3759
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (with implicit [data-binding](#data-binding) to raw data rows):
3484
3760
 
3761
+ ```ruby
3485
3762
  require 'glimmer-dsl-libui'
3486
3763
 
3487
3764
  include Glimmer
3488
3765
 
3489
- img = image(File.expand_path('../icons/glimmer.png', __dir__), 24, 24)
3766
+ 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)
3490
3767
 
3491
3768
  data = [
3492
3769
  [['cat', :red] , ['meow', :blue] , [true, 'mammal', :green], [img, 'Glimmer', :dark_blue], {r: 255, g: 120, b: 0, a: 0.5}],
@@ -3503,7 +3780,7 @@ window('Animals', 500, 200) {
3503
3780
  text_color_column('Sound')
3504
3781
  checkbox_text_color_column('Description')
3505
3782
  image_text_color_column('GUI')
3506
- background_color_column('Mammal')
3783
+ background_color_column # must be the last column
3507
3784
 
3508
3785
  cell_rows data
3509
3786
  }
@@ -3511,7 +3788,7 @@ window('Animals', 500, 200) {
3511
3788
  }.show
3512
3789
  ```
3513
3790
 
3514
- 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`):
3791
+ 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`):
3515
3792
 
3516
3793
  ```ruby
3517
3794
  require 'glimmer-dsl-libui'
@@ -3545,7 +3822,7 @@ window('Animals', 500, 200) {
3545
3822
  text_color_column('Sound')
3546
3823
  checkbox_text_color_column('Description')
3547
3824
  image_text_color_column('GUI')
3548
- background_color_column('Mammal')
3825
+ background_color_column
3549
3826
 
3550
3827
  cell_rows data
3551
3828
  }
@@ -3687,9 +3964,9 @@ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/
3687
3964
  ruby -r glimmer-dsl-libui -e "require 'examples/basic_scrolling_area'"
3688
3965
  ```
3689
3966
 
3690
- Mac | Linux
3691
- ----|------
3692
- ![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)
3967
+ Mac | Windows | Linux
3968
+ ----|---------|------
3969
+ ![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)
3693
3970
 
3694
3971
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3695
3972
 
@@ -3776,6 +4053,8 @@ BasicScrollingArea.new.launch
3776
4053
 
3777
4054
  #### Basic Image
3778
4055
 
4056
+ Please note the caveats of [Area Image](#area-image) **(Alpha Feature)** with regards to this example.
4057
+
3779
4058
  [examples/basic_image.rb](examples/basic_image.rb)
3780
4059
 
3781
4060
  Run with this command from the root of the project if you cloned the project:
@@ -5026,9 +5305,9 @@ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/
5026
5305
  ruby -r glimmer-dsl-libui -e "require 'examples/button_counter'"
5027
5306
  ```
5028
5307
 
5029
- Mac | Linux
5030
- ----|------
5031
- ![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)
5308
+ Mac | Windows | Linux
5309
+ ----|---------|------
5310
+ ![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)
5032
5311
 
5033
5312
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
5034
5313
 
@@ -5687,9 +5966,74 @@ MAIN_WINDOW = window('Control Gallery', 600, 500) {
5687
5966
  }
5688
5967
  }
5689
5968
  }
5690
- }
5691
-
5692
- MAIN_WINDOW.show
5969
+ }
5970
+
5971
+ MAIN_WINDOW.show
5972
+ ```
5973
+
5974
+ #### CPU Percentage
5975
+
5976
+ This example shows CPU usage percentage second by second.
5977
+
5978
+ Note that it is highly dependent on low-level OS terminal commands, so if anything changes in their output formatting, the code could break. Please report any issues you might encounter.
5979
+
5980
+ [examples/cpu_percentage.rb](examples/cpu_percentage.rb)
5981
+
5982
+ Run with this command from the root of the project if you cloned the project:
5983
+
5984
+ ```
5985
+ ruby -r './lib/glimmer-dsl-libui' examples/cpu_percentage.rb
5986
+ ```
5987
+
5988
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
5989
+
5990
+ ```
5991
+ ruby -r glimmer-dsl-libui -e "require 'examples/cpu_percentage'"
5992
+ ```
5993
+
5994
+ Mac | Windows | Linux
5995
+ ----|---------|------
5996
+ ![glimmer-dsl-libui-mac-cpu-percentage.png](images/glimmer-dsl-libui-mac-cpu-percentage.png) | ![glimmer-dsl-libui-windows-cpu-percentage.png](images/glimmer-dsl-libui-windows-cpu-percentage.png) | ![glimmer-dsl-libui-linux-cpu-percentage.png](images/glimmer-dsl-libui-linux-cpu-percentage.png)
5997
+
5998
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
5999
+
6000
+ ```ruby
6001
+ require 'glimmer-dsl-libui'
6002
+ require 'bigdecimal'
6003
+
6004
+ include Glimmer
6005
+
6006
+ data = [
6007
+ ['CPU', '0%', 0],
6008
+ ]
6009
+
6010
+ Glimmer::LibUI.timer(1) do
6011
+ cpu_percentage_value = nil
6012
+ if OS.windows?
6013
+ cpu_percentage_raw_value = `wmic cpu get loadpercentage`
6014
+ cpu_percentage_value = cpu_percentage_raw_value.split("\n")[2].to_i
6015
+ elsif OS.mac?
6016
+ cpu_percentage_value = `ps -A -o %cpu | awk '{s+=$1} END {print s}'`.to_i
6017
+ elsif OS.linux?
6018
+ stats = `top -n 1`
6019
+ idle_percentage = stats.split("\n")[2].match(/ni,.* (.*) .*id/)[1]
6020
+ cpu_percentage_value = (BigDecimal(100) - BigDecimal(idle_percentage)).to_i
6021
+ end
6022
+ data[0][1] = "#{cpu_percentage_value}%"
6023
+ data[0][2] = cpu_percentage_value
6024
+ end
6025
+
6026
+ window('CPU Percentage', 400, 200) {
6027
+ vertical_box {
6028
+ table {
6029
+ text_column('Name')
6030
+ text_column('Value')
6031
+ progress_bar_column('Percentage')
6032
+
6033
+ cell_rows data # implicit data-binding
6034
+ }
6035
+ }
6036
+ }.show
5693
6037
  ```
5694
6038
 
5695
6039
  #### Custom Draw Text
@@ -6402,8 +6746,8 @@ window('Editable animal sounds', 300, 200) {
6402
6746
  text_column('Animal')
6403
6747
  text_column('Description')
6404
6748
 
6405
- cell_rows data
6406
6749
  editable true
6750
+ cell_rows data
6407
6751
 
6408
6752
  on_changed do |row, type, row_data| # fires on all changes (even ones happening through data array)
6409
6753
  puts "Row #{row} #{type}: #{row_data}"
@@ -6418,30 +6762,382 @@ window('Editable animal sounds', 300, 200) {
6418
6762
  on_closing do
6419
6763
  puts 'Bye Bye'
6420
6764
  end
6421
- }.show
6422
- ```
6423
-
6424
- #### Form Table
6425
-
6426
- [examples/form_table.rb](examples/form_table.rb)
6427
-
6428
- Run with this command from the root of the project if you cloned the project:
6429
-
6430
- ```
6431
- ruby -r './lib/glimmer-dsl-libui' examples/form_table.rb
6432
- ```
6433
-
6434
- Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
6765
+ }.show
6766
+ ```
6767
+
6768
+ #### Form Table
6769
+
6770
+ [examples/form_table.rb](examples/form_table.rb)
6771
+
6772
+ Run with this command from the root of the project if you cloned the project:
6773
+
6774
+ ```
6775
+ ruby -r './lib/glimmer-dsl-libui' examples/form_table.rb
6776
+ ```
6777
+
6778
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
6779
+
6780
+ ```
6781
+ ruby -r glimmer-dsl-libui -e "require 'examples/form_table'"
6782
+ ```
6783
+
6784
+ Mac | Windows | Linux
6785
+ ----|---------|------
6786
+ ![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)
6787
+
6788
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
6789
+
6790
+ ```ruby
6791
+ require 'glimmer-dsl-libui'
6792
+
6793
+ class FormTable
6794
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
6795
+
6796
+ include Glimmer
6797
+
6798
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
6799
+
6800
+ def initialize
6801
+ @contacts = [
6802
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
6803
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
6804
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
6805
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
6806
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
6807
+ ]
6808
+ end
6809
+
6810
+ def launch
6811
+ window('Contacts', 600, 600) { |w|
6812
+ margined true
6813
+
6814
+ vertical_box {
6815
+ form {
6816
+ stretchy false
6817
+
6818
+ entry {
6819
+ label 'Name'
6820
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6821
+ }
6822
+
6823
+ entry {
6824
+ label 'Email'
6825
+ text <=> [self, :email]
6826
+ }
6827
+
6828
+ entry {
6829
+ label 'Phone'
6830
+ text <=> [self, :phone]
6831
+ }
6832
+
6833
+ entry {
6834
+ label 'City'
6835
+ text <=> [self, :city]
6836
+ }
6837
+
6838
+ entry {
6839
+ label 'State'
6840
+ text <=> [self, :state]
6841
+ }
6842
+ }
6843
+
6844
+ button('Save Contact') {
6845
+ stretchy false
6846
+
6847
+ on_clicked do
6848
+ new_row = [name, email, phone, city, state]
6849
+ if new_row.include?('')
6850
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6851
+ else
6852
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to explicit data-binding
6853
+ @unfiltered_contacts = @contacts.dup
6854
+ self.name = '' # automatically clears name entry through explicit data-binding
6855
+ self.email = ''
6856
+ self.phone = ''
6857
+ self.city = ''
6858
+ self.state = ''
6859
+ end
6860
+ end
6861
+ }
6862
+
6863
+ search_entry {
6864
+ stretchy false
6865
+ # bidirectional data-binding of text to self.filter_value with after_write option
6866
+ text <=> [self, :filter_value,
6867
+ after_write: ->(filter_value) { # execute after write to self.filter_value
6868
+ @unfiltered_contacts ||= @contacts.dup
6869
+ # Unfilter first to remove any previous filters
6870
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
6871
+ # Now, apply filter if entered
6872
+ unless filter_value.empty?
6873
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
6874
+ contact.members.any? do |attribute|
6875
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
6876
+ end
6877
+ end
6878
+ end
6879
+ }
6880
+ ]
6881
+ }
6882
+
6883
+ table {
6884
+ text_column('Name')
6885
+ text_column('Email')
6886
+ text_column('Phone')
6887
+ text_column('City')
6888
+ text_column('State')
6889
+
6890
+ editable true
6891
+ cell_rows <=> [self, :contacts] # explicit data-binding to Model Array auto-inferring model attribute names from underscored table column names by convention
6892
+
6893
+ on_changed do |row, type, row_data|
6894
+ puts "Row #{row} #{type}: #{row_data}"
6895
+ end
6896
+ }
6897
+ }
6898
+ }.show
6899
+ end
6900
+ end
6901
+
6902
+ FormTable.new.launch
6903
+ ```
6904
+
6905
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
6906
+
6907
+ ```ruby
6908
+ require 'glimmer-dsl-libui'
6909
+
6910
+ class FormTable
6911
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
6912
+
6913
+ include Glimmer
6914
+
6915
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
6916
+
6917
+ def initialize
6918
+ @contacts = [
6919
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
6920
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
6921
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
6922
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
6923
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
6924
+ ]
6925
+ end
6926
+
6927
+ def launch
6928
+ window('Contacts', 600, 600) { |w|
6929
+ margined true
6930
+
6931
+ vertical_box {
6932
+ form {
6933
+ stretchy false
6934
+
6935
+ entry {
6936
+ label 'Name'
6937
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6938
+ }
6939
+
6940
+ entry {
6941
+ label 'Email'
6942
+ text <=> [self, :email]
6943
+ }
6944
+
6945
+ entry {
6946
+ label 'Phone'
6947
+ text <=> [self, :phone]
6948
+ }
6949
+
6950
+ entry {
6951
+ label 'City'
6952
+ text <=> [self, :city]
6953
+ }
6954
+
6955
+ entry {
6956
+ label 'State'
6957
+ text <=> [self, :state]
6958
+ }
6959
+ }
6960
+
6961
+ button('Save Contact') {
6962
+ stretchy false
6963
+
6964
+ on_clicked do
6965
+ new_row = [name, email, phone, city, state]
6966
+ if new_row.include?('')
6967
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6968
+ else
6969
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to implicit data-binding
6970
+ @unfiltered_contacts = @contacts.dup
6971
+ self.name = '' # automatically clears name entry through explicit data-binding
6972
+ self.email = ''
6973
+ self.phone = ''
6974
+ self.city = ''
6975
+ self.state = ''
6976
+ end
6977
+ end
6978
+ }
6979
+
6980
+ search_entry {
6981
+ stretchy false
6982
+ # bidirectional data-binding of text to self.filter_value with after_write option
6983
+ text <=> [self, :filter_value,
6984
+ after_write: ->(filter_value) { # execute after write to self.filter_value
6985
+ @unfiltered_contacts ||= @contacts.dup
6986
+ # Unfilter first to remove any previous filters
6987
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
6988
+ # Now, apply filter if entered
6989
+ unless filter_value.empty?
6990
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
6991
+ contact.members.any? do |attribute|
6992
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
6993
+ end
6994
+ end
6995
+ end
6996
+ }
6997
+ ]
6998
+ }
6999
+
7000
+ table {
7001
+ text_column('Name')
7002
+ text_column('Email')
7003
+ text_column('Phone')
7004
+ text_column('City')
7005
+ text_column('State/Province')
7006
+
7007
+ editable true
7008
+ cell_rows <=> [self, :contacts, column_attributes: {'State/Province' => :state}] # explicit data-binding to Model Array with column_attributes mapping for a specific column
7009
+
7010
+ on_changed do |row, type, row_data|
7011
+ puts "Row #{row} #{type}: #{row_data}"
7012
+ end
7013
+ }
7014
+ }
7015
+ }.show
7016
+ end
7017
+ end
7018
+
7019
+ FormTable.new.launch
7020
+ ```
7021
+
7022
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
7023
+
7024
+ ```ruby
7025
+
7026
+ require 'glimmer-dsl-libui'
7027
+
7028
+ class FormTable
7029
+ Contact = Struct.new(:full_name, :email_address, :phone_number, :city_or_town, :state_or_province)
7030
+
7031
+ include Glimmer
7032
+
7033
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
7034
+
7035
+ def initialize
7036
+ @contacts = [
7037
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
7038
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
7039
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
7040
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
7041
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
7042
+ ]
7043
+ end
7044
+
7045
+ def launch
7046
+ window('Contacts', 600, 600) { |w|
7047
+ margined true
7048
+
7049
+ vertical_box {
7050
+ form {
7051
+ stretchy false
7052
+
7053
+ entry {
7054
+ label 'Name'
7055
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
7056
+ }
7057
+
7058
+ entry {
7059
+ label 'Email'
7060
+ text <=> [self, :email]
7061
+ }
7062
+
7063
+ entry {
7064
+ label 'Phone'
7065
+ text <=> [self, :phone]
7066
+ }
7067
+
7068
+ entry {
7069
+ label 'City'
7070
+ text <=> [self, :city]
7071
+ }
7072
+
7073
+ entry {
7074
+ label 'State'
7075
+ text <=> [self, :state]
7076
+ }
7077
+ }
7078
+
7079
+ button('Save Contact') {
7080
+ stretchy false
7081
+
7082
+ on_clicked do
7083
+ new_row = [name, email, phone, city, state]
7084
+ if new_row.include?('')
7085
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
7086
+ else
7087
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to implicit data-binding
7088
+ @unfiltered_contacts = @contacts.dup
7089
+ self.name = '' # automatically clears name entry through explicit data-binding
7090
+ self.email = ''
7091
+ self.phone = ''
7092
+ self.city = ''
7093
+ self.state = ''
7094
+ end
7095
+ end
7096
+ }
7097
+
7098
+ search_entry {
7099
+ stretchy false
7100
+ # bidirectional data-binding of text to self.filter_value with after_write option
7101
+ text <=> [self, :filter_value,
7102
+ after_write: ->(filter_value) { # execute after write to self.filter_value
7103
+ @unfiltered_contacts ||= @contacts.dup
7104
+ # Unfilter first to remove any previous filters
7105
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
7106
+ # Now, apply filter if entered
7107
+ unless filter_value.empty?
7108
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
7109
+ contact.members.any? do |attribute|
7110
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
7111
+ end
7112
+ end
7113
+ end
7114
+ }
7115
+ ]
7116
+ }
7117
+
7118
+ table {
7119
+ text_column('Name')
7120
+ text_column('Email')
7121
+ text_column('Phone')
7122
+ text_column('City')
7123
+ text_column('State')
7124
+
7125
+ editable true
7126
+ 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
7127
+
7128
+ on_changed do |row, type, row_data|
7129
+ puts "Row #{row} #{type}: #{row_data}"
7130
+ end
7131
+ }
7132
+ }
7133
+ }.show
7134
+ end
7135
+ end
6435
7136
 
7137
+ FormTable.new.launch
6436
7138
  ```
6437
- ruby -r glimmer-dsl-libui -e "require 'examples/form_table'"
6438
- ```
6439
-
6440
- Mac | Windows | Linux
6441
- ----|---------|------
6442
- ![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)
6443
7139
 
6444
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
7140
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (with explicit [data-binding](#data-binding) to raw data):
6445
7141
 
6446
7142
  ```ruby
6447
7143
  require 'glimmer-dsl-libui'
@@ -6449,15 +7145,15 @@ require 'glimmer-dsl-libui'
6449
7145
  class FormTable
6450
7146
  include Glimmer
6451
7147
 
6452
- attr_accessor :name, :email, :phone, :city, :state, :filter_value
7148
+ attr_accessor :data, :name, :email, :phone, :city, :state, :filter_value
6453
7149
 
6454
7150
  def initialize
6455
7151
  @data = [
6456
- ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
6457
- ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
6458
- ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
6459
- ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
6460
- ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
7152
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],
7153
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],
7154
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],
7155
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],
7156
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
6461
7157
  ]
6462
7158
  end
6463
7159
 
@@ -6471,7 +7167,7 @@ class FormTable
6471
7167
 
6472
7168
  entry {
6473
7169
  label 'Name'
6474
- text <=> [self, :name]
7170
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6475
7171
  }
6476
7172
 
6477
7173
  entry {
@@ -6503,8 +7199,8 @@ class FormTable
6503
7199
  if new_row.include?('')
6504
7200
  msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6505
7201
  else
6506
- @data << new_row # automatically inserts a row into the table due to implicit data-binding
6507
- @unfiltered_data = @data.dup
7202
+ data << new_row # automatically inserts a row into the table due to implicit data-binding
7203
+ @unfiltered_data = data.dup
6508
7204
  self.name = '' # automatically clears name entry through explicit data-binding
6509
7205
  self.email = ''
6510
7206
  self.phone = ''
@@ -6516,14 +7212,15 @@ class FormTable
6516
7212
 
6517
7213
  search_entry {
6518
7214
  stretchy false
6519
- text <=> [self, :filter_value, # bidirectional data-binding of text to self.filter_value with after_write option
7215
+ # bidirectional data-binding of text to self.filter_value with after_write option
7216
+ text <=> [self, :filter_value,
6520
7217
  after_write: ->(filter_value) { # execute after write to self.filter_value
6521
- @unfiltered_data ||= @data.dup
7218
+ @unfiltered_data ||= data.dup
6522
7219
  # Unfilter first to remove any previous filters
6523
- @data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
7220
+ data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
6524
7221
  # Now, apply filter if entered
6525
7222
  unless filter_value.empty?
6526
- @data.filter! do |row_data| # affects table indirectly through implicit data-binding
7223
+ data.filter! do |row_data| # affects table indirectly through implicit data-binding
6527
7224
  row_data.any? do |cell|
6528
7225
  cell.to_s.downcase.include?(filter_value.downcase)
6529
7226
  end
@@ -6539,8 +7236,9 @@ class FormTable
6539
7236
  text_column('Phone')
6540
7237
  text_column('City')
6541
7238
  text_column('State')
6542
-
6543
- cell_rows @data # implicit data-binding
7239
+
7240
+ editable true
7241
+ cell_rows <=> [self, :data] # explicit data-binding to raw data Array of Arrays
6544
7242
 
6545
7243
  on_changed do |row, type, row_data|
6546
7244
  puts "Row #{row} #{type}: #{row_data}"
@@ -6554,7 +7252,7 @@ end
6554
7252
  FormTable.new.launch
6555
7253
  ```
6556
7254
 
6557
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
7255
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 5 (with implicit [data-binding](#data-binding)):
6558
7256
 
6559
7257
  ```ruby
6560
7258
  require 'glimmer-dsl-libui'
@@ -6562,11 +7260,11 @@ require 'glimmer-dsl-libui'
6562
7260
  include Glimmer
6563
7261
 
6564
7262
  data = [
6565
- ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
6566
- ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
6567
- ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
6568
- ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
6569
- ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
7263
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],
7264
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],
7265
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],
7266
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],
7267
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
6570
7268
  ]
6571
7269
 
6572
7270
  window('Contacts', 600, 600) { |w|
@@ -6642,7 +7340,8 @@ window('Contacts', 600, 600) { |w|
6642
7340
  text_column('City')
6643
7341
  text_column('State')
6644
7342
 
6645
- cell_rows data # implicit data-binding
7343
+ editable true
7344
+ cell_rows data # implicit data-binding to raw data Array of Arrays
6646
7345
 
6647
7346
  on_changed do |row, type, row_data|
6648
7347
  puts "Row #{row} #{type}: #{row_data}"
@@ -8178,7 +8877,7 @@ Mac | Windows | Linux
8178
8877
  ----|---------|------
8179
8878
  ![glimmer-dsl-libui-mac-snake.png](images/glimmer-dsl-libui-mac-snake.png) ![glimmer-dsl-libui-mac-snake-game-over.png](images/glimmer-dsl-libui-mac-snake-game-over.png) | ![glimmer-dsl-libui-windows-snake.png](images/glimmer-dsl-libui-windows-snake.png) ![glimmer-dsl-libui-windows-snake-game-over.png](images/glimmer-dsl-libui-windows-snake-game-over.png) | ![glimmer-dsl-libui-linux-snake.png](images/glimmer-dsl-libui-linux-snake.png) ![glimmer-dsl-libui-linux-snake-game-over.png](images/glimmer-dsl-libui-linux-snake-game-over.png)
8180
8879
 
8181
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
8880
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
8182
8881
 
8183
8882
  ```ruby
8184
8883
  require 'glimmer-dsl-libui'
@@ -8195,6 +8894,7 @@ class Snake
8195
8894
  @game = Model::Game.new
8196
8895
  @grid = Presenter::Grid.new(@game)
8197
8896
  @game.start
8897
+ @keypress_queue = []
8198
8898
  create_gui
8199
8899
  register_observers
8200
8900
  end
@@ -8214,14 +8914,30 @@ class Snake
8214
8914
  end
8215
8915
 
8216
8916
  Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
8217
- @game.snake.move unless @game.over?
8917
+ unless @game.over?
8918
+ process_queued_keypress
8919
+ @game.snake.move
8920
+ end
8921
+ end
8922
+ end
8923
+
8924
+ def process_queued_keypress
8925
+ # key press queue ensures one turn per snake move to avoid a double-turn resulting in instant death (due to snake illogically going back against itself)
8926
+ key = @keypress_queue.shift
8927
+ case [@game.snake.head.orientation, key]
8928
+ in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
8929
+ @game.snake.turn_right
8930
+ in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
8931
+ @game.snake.turn_left
8932
+ else
8933
+ # No Op
8218
8934
  end
8219
8935
  end
8220
8936
 
8221
8937
  def create_gui
8222
8938
  @main_window = window {
8223
8939
  # data-bind window title to game score, converting it to a title string on read from the model
8224
- title <= [@game, :score, on_read: -> (score) {"Glimmer Snake (Score: #{@game.score})"}]
8940
+ title <= [@game, :score, on_read: -> (score) {"Snake (Score: #{@game.score})"}]
8225
8941
  content_size @game.width * CELL_SIZE, @game.height * CELL_SIZE
8226
8942
  resizable false
8227
8943
 
@@ -8239,15 +8955,109 @@ class Snake
8239
8955
  }
8240
8956
 
8241
8957
  on_key_up do |area_key_event|
8242
- orientation_and_key = [@game.snake.head.orientation, area_key_event[:ext_key]]
8243
- case orientation_and_key
8244
- in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
8245
- @game.snake.turn_right
8246
- in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
8247
- @game.snake.turn_left
8248
- else
8249
- # No Op
8250
- end
8958
+ @keypress_queue << area_key_event[:ext_key]
8959
+ end
8960
+ }
8961
+ end
8962
+ }
8963
+ end
8964
+ }
8965
+ }
8966
+ end
8967
+ end
8968
+
8969
+ Snake.new.launch
8970
+ ```
8971
+
8972
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
8973
+
8974
+ ```ruby
8975
+ require 'glimmer-dsl-libui'
8976
+
8977
+ require_relative 'snake/presenter/grid'
8978
+
8979
+ class Snake
8980
+ include Glimmer
8981
+
8982
+ CELL_SIZE = 15
8983
+ SNAKE_MOVE_DELAY = 0.1
8984
+
8985
+ def initialize
8986
+ @game = Model::Game.new
8987
+ @grid = Presenter::Grid.new(@game)
8988
+ @game.start
8989
+ @keypress_queue = []
8990
+ create_gui
8991
+ register_observers
8992
+ end
8993
+
8994
+ def launch
8995
+ @main_window.show
8996
+ end
8997
+
8998
+ def register_observers
8999
+ @game.height.times do |row|
9000
+ @game.width.times do |column|
9001
+ observe(@grid.cells[row][column], :color) do |new_color|
9002
+ @cell_grid[row][column].fill = new_color
9003
+ end
9004
+ end
9005
+ end
9006
+
9007
+ observe(@game, :over) do |game_over|
9008
+ Glimmer::LibUI.queue_main do
9009
+ if game_over
9010
+ msg_box('Game Over!', "Score: #{@game.score} | High Score: #{@game.high_score}")
9011
+ @game.start
9012
+ end
9013
+ end
9014
+ end
9015
+
9016
+ Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
9017
+ unless @game.over?
9018
+ process_queued_keypress
9019
+ @game.snake.move
9020
+ end
9021
+ end
9022
+ end
9023
+
9024
+ def process_queued_keypress
9025
+ # key press queue ensures one turn per snake move to avoid a double-turn resulting in instant death (due to snake illogically going back against itself)
9026
+ key = @keypress_queue.shift
9027
+ case [@game.snake.head.orientation, key]
9028
+ in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
9029
+ @game.snake.turn_right
9030
+ in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
9031
+ @game.snake.turn_left
9032
+ else
9033
+ # No Op
9034
+ end
9035
+ end
9036
+
9037
+ def create_gui
9038
+ @cell_grid = []
9039
+ @main_window = window {
9040
+ # data-bind window title to game score, converting it to a title string on read from the model
9041
+ title <= [@game, :score, on_read: -> (score) {"Snake (Score: #{@game.score})"}]
9042
+ content_size @game.width * CELL_SIZE, @game.height * CELL_SIZE
9043
+ resizable false
9044
+
9045
+ vertical_box {
9046
+ padded false
9047
+
9048
+ @game.height.times do |row|
9049
+ @cell_grid << []
9050
+ horizontal_box {
9051
+ padded false
9052
+
9053
+ @game.width.times do |column|
9054
+ area {
9055
+ @cell_grid.last << square(0, 0, CELL_SIZE) {
9056
+ fill Presenter::Cell::COLOR_CLEAR
9057
+ }
9058
+
9059
+ on_key_up do |area_key_event|
9060
+ @keypress_queue << area_key_event[:ext_key]
8251
9061
  end
8252
9062
  }
8253
9063
  end
@@ -8676,7 +9486,7 @@ Mac | Windows | Linux
8676
9486
  ----|---------|------
8677
9487
  ![glimmer-dsl-libui-mac-tic-tac-toe.png](images/glimmer-dsl-libui-mac-tic-tac-toe.png) ![glimmer-dsl-libui-mac-tic-tac-toe-player-o-wins.png](images/glimmer-dsl-libui-mac-tic-tac-toe-player-o-wins.png) ![glimmer-dsl-libui-mac-tic-tac-toe-player-x-wins.png](images/glimmer-dsl-libui-mac-tic-tac-toe-player-x-wins.png) ![glimmer-dsl-libui-mac-tic-tac-toe-draw.png](images/glimmer-dsl-libui-mac-tic-tac-toe-draw.png) | ![glimmer-dsl-libui-windows-tic-tac-toe.png](images/glimmer-dsl-libui-windows-tic-tac-toe.png) ![glimmer-dsl-libui-windows-tic-tac-toe-player-o-wins.png](images/glimmer-dsl-libui-windows-tic-tac-toe-player-o-wins.png) ![glimmer-dsl-libui-windows-tic-tac-toe-player-x-wins.png](images/glimmer-dsl-libui-windows-tic-tac-toe-player-x-wins.png) ![glimmer-dsl-libui-windows-tic-tac-toe-draw.png](images/glimmer-dsl-libui-windows-tic-tac-toe-draw.png) | ![glimmer-dsl-libui-linux-tic-tac-toe.png](images/glimmer-dsl-libui-linux-tic-tac-toe.png) ![glimmer-dsl-libui-linux-tic-tac-toe-player-o-wins.png](images/glimmer-dsl-libui-linux-tic-tac-toe-player-o-wins.png) ![glimmer-dsl-libui-linux-tic-tac-toe-player-x-wins.png](images/glimmer-dsl-libui-linux-tic-tac-toe-player-x-wins.png) ![glimmer-dsl-libui-linux-tic-tac-toe-draw.png](images/glimmer-dsl-libui-linux-tic-tac-toe-draw.png)
8678
9488
 
8679
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
9489
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
8680
9490
 
8681
9491
  ```ruby
8682
9492
  require 'glimmer-dsl-libui'
@@ -8722,6 +9532,7 @@ class TicTacToe
8722
9532
  text(23, 19) {
8723
9533
  string {
8724
9534
  font family: 'Arial', size: OS.mac? ? 20 : 16
9535
+ # data-bind string property of area text attributed string to tic tac toe board cell sign
8725
9536
  string <= [@tic_tac_toe_board[row + 1, column + 1], :sign] # board model is 1-based
8726
9537
  }
8727
9538
  }
@@ -8755,6 +9566,95 @@ end
8755
9566
  TicTacToe.new.launch
8756
9567
  ```
8757
9568
 
9569
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
9570
+
9571
+ ```ruby
9572
+
9573
+ require 'glimmer-dsl-libui'
9574
+
9575
+ require_relative "tic_tac_toe/board"
9576
+
9577
+ class TicTacToe
9578
+ include Glimmer
9579
+
9580
+ def initialize
9581
+ @tic_tac_toe_board = Board.new
9582
+ end
9583
+
9584
+ def launch
9585
+ create_gui
9586
+ register_observers
9587
+ @main_window.show
9588
+ end
9589
+
9590
+ def register_observers
9591
+ observe(@tic_tac_toe_board, :game_status) do |game_status|
9592
+ display_win_message if game_status == Board::WIN
9593
+ display_draw_message if game_status == Board::DRAW
9594
+ end
9595
+
9596
+ 3.times.map do |row|
9597
+ 3.times.map do |column|
9598
+ observe(@tic_tac_toe_board[row + 1, column + 1], :sign) do |sign| # board model is 1-based
9599
+ @cells[row][column].string = sign
9600
+ end
9601
+ end
9602
+ end
9603
+ end
9604
+
9605
+ def create_gui
9606
+ @main_window = window('Tic-Tac-Toe', 180, 180) {
9607
+ resizable false
9608
+
9609
+ @cells = []
9610
+ vertical_box {
9611
+ padded false
9612
+
9613
+ 3.times.map do |row|
9614
+ @cells << []
9615
+ horizontal_box {
9616
+ padded false
9617
+
9618
+ 3.times.map do |column|
9619
+ area {
9620
+ square(0, 0, 60) {
9621
+ stroke :black, thickness: 2
9622
+ }
9623
+ text(23, 19) {
9624
+ @cells[row] << string('') {
9625
+ font family: 'Arial', size: OS.mac? ? 20 : 16
9626
+ }
9627
+ }
9628
+ on_mouse_up do
9629
+ @tic_tac_toe_board.mark(row + 1, column + 1) # board model is 1-based
9630
+ end
9631
+ }
9632
+ end
9633
+ }
9634
+ end
9635
+ }
9636
+ }
9637
+ end
9638
+
9639
+ def display_win_message
9640
+ display_game_over_message("Player #{@tic_tac_toe_board.winning_sign} has won!")
9641
+ end
9642
+
9643
+ def display_draw_message
9644
+ display_game_over_message("Draw!")
9645
+ end
9646
+
9647
+ def display_game_over_message(message_text)
9648
+ Glimmer::LibUI.queue_main do
9649
+ msg_box('Game Over', message_text)
9650
+ @tic_tac_toe_board.reset!
9651
+ end
9652
+ end
9653
+ end
9654
+
9655
+ TicTacToe.new.launch
9656
+ ```
9657
+
8758
9658
  #### Timer
8759
9659
 
8760
9660
  To run this example, install [TiMidity](http://timidity.sourceforge.net) and ensure `timidity` command is in `PATH` (can be installed via [Homebrew](https://brew.sh) on Mac or [apt-get](https://help.ubuntu.com/community/AptGet/Howto) on Linux).
@@ -9115,7 +10015,7 @@ These features have been planned or suggested. You might see them in a future ve
9115
10015
  is fine, but please isolate to its own commit so I can cherry-pick
9116
10016
  around it.
9117
10017
 
9118
- Note that the latest development sometimes takes place in [development](https://github.com/AndyObtiva/glimmer-dsl-libui/tree/development) branch (which is deleted once merged back to [master](https://github.com/AndyObtiva/glimmer-dsl-libui)).
10018
+ Note that the latest development sometimes takes place in the [development](https://github.com/AndyObtiva/glimmer-dsl-libui/tree/development) branch (usually deleted once merged back to [master](https://github.com/AndyObtiva/glimmer-dsl-libui)).
9119
10019
 
9120
10020
  ## Contributors
9121
10021