glimmer-dsl-libui 0.4.8 → 0.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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