glimmer-dsl-libui 0.4.9 → 0.4.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/README.md +1330 -487
  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/snake2.rb +97 -0
  25. data/examples/tic_tac_toe.rb +1 -0
  26. data/examples/tic_tac_toe2.rb +84 -0
  27. data/glimmer-dsl-libui.gemspec +0 -0
  28. data/lib/glimmer/dsl/libui/control_expression.rb +2 -1
  29. data/lib/glimmer/dsl/libui/shape_expression.rb +2 -2
  30. data/lib/glimmer/dsl/libui/string_expression.rb +2 -1
  31. data/lib/glimmer/libui/attributed_string.rb +3 -2
  32. data/lib/glimmer/libui/control_proxy/column/background_color_column_proxy.rb +4 -0
  33. data/lib/glimmer/libui/control_proxy/image_proxy.rb +16 -0
  34. data/lib/glimmer/libui/control_proxy/table_proxy.rb +95 -29
  35. data/lib/glimmer/libui/control_proxy.rb +4 -2
  36. data/lib/glimmer/libui/data_bindable.rb +8 -3
  37. data/lib/glimmer/libui/shape.rb +3 -2
  38. data/lib/glimmer/libui.rb +2 -2
  39. data/lib/glimmer-dsl-libui.rb +1 -0
  40. 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.9
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.13
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.9'
503
+ gem 'glimmer-dsl-libui', '~> 0.4.13'
504
+ ```
505
+
506
+ Test that installation worked by running the [Meta-Example](#examples):
507
+
508
+ ```
509
+ ruby -r glimmer-dsl-libui -e "require 'examples/meta_example'"
377
510
  ```
378
511
 
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.
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,155 +752,145 @@ 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
 
618
- Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
619
-
620
- ```ruby
621
- require 'glimmer-dsl-libui'
622
-
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
637
-
638
- def launch
639
- window('Contacts', 600, 600) { |w|
640
- margined true
641
-
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 = ''
687
- end
688
- 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')
716
-
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
727
-
728
- FormTable.new.launch
729
- ```
730
-
731
- ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png)
732
-
733
- Learn more by checking out [examples](#examples).
734
-
735
- ### Area API
736
-
737
- The `area` control is a canvas-like control for drawing paths that can be used in one of two ways:
738
- - Declaratively via stable paths: useful for stable paths that will not change often later on. Simply nest `path` and figures like `rectangle` and all drawing logic is generated automatically. Path proxy objects are preserved across redraws assuming there would be relatively few stable paths (mostly for decorative reasons).
739
- - Semi-declaratively via on_draw listener dynamic paths: useful for more dynamic paths that will definitely change very often. Open an `on_draw` listener block that receives an [`area_draw_params`](#area-draw-params) argument and nest `path` and figures like `rectangle` and all drawing logic is generated automatically. Path proxy objects are destroyed (thrown-away) at the end of drawing, thus having less memory overhead for drawing thousands of dynamic paths.
740
-
741
- Note that when nesting an `area` directly underneath `window` (without a layout control like `vertical_box`), it is automatically reparented with `vertical_box` in between the `window` and `area` since it would not show up on Linux otherwise.
755
+ ([explicit data-binding](#data-binding) supports everything available with implicit data-binding too)
742
756
 
743
- Here is an example of a declarative `area` with a stable path (you may copy/paste in [`girb`](#girb-glimmer-irb)):
757
+ Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
744
758
 
745
759
  ```ruby
746
760
  require 'glimmer-dsl-libui'
747
761
 
748
762
  include Glimmer
749
763
 
750
- window('Basic Area', 400, 400) {
764
+ data = [
765
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],
766
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],
767
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],
768
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],
769
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
770
+ ]
771
+
772
+ window('Contacts', 600, 600) { |w|
751
773
  margined true
752
774
 
753
775
  vertical_box {
754
- area {
755
- path { # a stable path is added declaratively
756
- rectangle(0, 0, 400, 400)
757
-
758
- fill r: 102, g: 102, b: 204, a: 1.0
776
+ form {
777
+ stretchy false
778
+
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'
759
797
  }
760
798
  }
761
- }
762
- }.show
763
- ```
764
-
765
- Mac | Windows | Linux
766
- ----|---------|------
799
+
800
+ button('Save Contact') {
801
+ stretchy false
802
+
803
+ on_clicked do
804
+ new_row = [@name_entry.text, @email_entry.text, @phone_entry.text, @city_entry.text, @state_entry.text]
805
+ if new_row.include?('')
806
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
807
+ else
808
+ data << new_row # automatically inserts a row into the table due to implicit data-binding
809
+ @unfiltered_data = data.dup
810
+ @name_entry.text = ''
811
+ @email_entry.text = ''
812
+ @phone_entry.text = ''
813
+ @city_entry.text = ''
814
+ @state_entry.text = ''
815
+ end
816
+ end
817
+ }
818
+
819
+ search_entry { |se|
820
+ stretchy false
821
+
822
+ on_changed do
823
+ filter_value = se.text
824
+ @unfiltered_data ||= data.dup
825
+ # Unfilter first to remove any previous filters
826
+ data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
827
+ # Now, apply filter if entered
828
+ unless filter_value.empty?
829
+ data.filter! do |row_data| # affects table indirectly through implicit data-binding
830
+ row_data.any? do |cell|
831
+ cell.to_s.downcase.include?(filter_value.downcase)
832
+ end
833
+ end
834
+ end
835
+ end
836
+ }
837
+
838
+ table {
839
+ text_column('Name')
840
+ text_column('Email')
841
+ text_column('Phone')
842
+ text_column('City')
843
+ text_column('State')
844
+
845
+ editable true
846
+ cell_rows data # implicit data-binding to raw data Array of Arrays
847
+
848
+ on_changed do |row, type, row_data|
849
+ puts "Row #{row} #{type}: #{row_data}"
850
+ end
851
+ }
852
+ }
853
+ }.show
854
+ ```
855
+
856
+ Mac | Windows | Linux
857
+ ----|---------|------
858
+ ![glimmer-dsl-libui-mac-form-table.png](images/glimmer-dsl-libui-mac-form-table.png) | ![glimmer-dsl-libui-windows-form-table.png](images/glimmer-dsl-libui-windows-form-table.png) | ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png)
859
+
860
+ Learn more by checking out [examples](#examples).
861
+
862
+ ### Area API
863
+
864
+ The `area` control is a canvas-like control for drawing paths that can be used in one of two ways:
865
+ - Declaratively via stable paths: useful for stable paths that will not change often later on. Simply nest `path` and figures like `rectangle` and all drawing logic is generated automatically. Path proxy objects are preserved across redraws assuming there would be relatively few stable paths (mostly for decorative reasons).
866
+ - Semi-declaratively via on_draw listener dynamic paths: useful for more dynamic paths that will definitely change very often. Open an `on_draw` listener block that receives an [`area_draw_params`](#area-draw-params) argument and nest `path` and figures like `rectangle` and all drawing logic is generated automatically. Path proxy objects are destroyed (thrown-away) at the end of drawing, thus having less memory overhead for drawing thousands of dynamic paths.
867
+
868
+ Note that when nesting an `area` directly underneath `window` (without a layout control like `vertical_box`), it is automatically reparented with `vertical_box` in between the `window` and `area` since it would not show up on Linux otherwise.
869
+
870
+ Here is an example of a declarative `area` with a stable path (you may copy/paste in [`girb`](#girb-glimmer-irb)):
871
+
872
+ ```ruby
873
+ require 'glimmer-dsl-libui'
874
+
875
+ include Glimmer
876
+
877
+ window('Basic Area', 400, 400) {
878
+ margined true
879
+
880
+ vertical_box {
881
+ area {
882
+ path { # a stable path is added declaratively
883
+ rectangle(0, 0, 400, 400)
884
+
885
+ fill r: 102, g: 102, b: 204, a: 1.0
886
+ }
887
+ }
888
+ }
889
+ }.show
890
+ ```
891
+
892
+ Mac | Windows | Linux
893
+ ----|---------|------
767
894
  ![glimmer-dsl-libui-mac-basic-area.png](images/glimmer-dsl-libui-mac-basic-area.png) | ![glimmer-dsl-libui-windows-basic-area.png](images/glimmer-dsl-libui-windows-basic-area.png) | ![glimmer-dsl-libui-linux-basic-area.png](images/glimmer-dsl-libui-linux-basic-area.png)
768
895
 
769
896
  Here is the same example using a semi-declarative `area` with `on_draw` listener that receives a [`area_draw_params`](#area-draw-params) argument and a dynamic path (you may copy/paste in [`girb`](#girb-glimmer-irb)):
@@ -798,17 +925,23 @@ 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
 
807
934
  #### Area Path Shapes
808
935
 
936
+ `area` can have geometric shapes drawn by adding `path` elements.
937
+
938
+ To add `path` shapes under an `area`, you can do so:
939
+ - Explicitly: by adding `path` under `area` and nesting shapes (e.g. `rectangle`) underneath that share the same `fill`/`stroke`/`transform` properties
940
+ - Implicitly: by adding shapes directly under `area` when the shapes have unique `fill`/`stroke`/`transform` properties ([Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) automatically constructs `path`s as intermediary parents for shapes directly added under `area`)
941
+
809
942
  `path` can receive a `draw_fill_mode` argument that can accept values `:winding` or `:alternate` and defaults to `:winding`.
810
943
 
811
- Available nested `path` shapes:
944
+ Available `path` shapes (that can be nested explicitly under `path` or implicitly under `area` directly):
812
945
  - `rectangle(x as Numeric, y as Numeric, width as Numeric, height as Numeric)`
813
946
  - `square(x as Numeric, y as Numeric, length as Numeric)`
814
947
  - `arc(x_center as Numeric, y_center as Numeric, radius as Numeric, start_angle as Numeric, sweep as Numeric, is_negative as Boolean)`
@@ -1216,6 +1349,7 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t
1216
1349
  - When destroying a control nested under a `horizontal_box` or `vertical_box`, it is automatically deleted from the box's children
1217
1350
  - When destroying a control nested under a `form`, it is automatically deleted from the form's children
1218
1351
  - When destroying a control nested under a `window` or `group`, it is automatically unset as their child to allow successful destruction
1352
+ - When destroying a control that has a data-binding to a model attribute, the data-binding observer registration is automatically deregistered
1219
1353
  - 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
1354
  - 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
1355
  - 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`)
@@ -1226,7 +1360,8 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t
1226
1360
  - Automatically provide shifted `:key` characters in `area_key_event` provided in `area` key listeners `on_key_event`, `on_key_down`, and `on_key_up`
1227
1361
  - `scrolling_area` `width` and `height` default to main window width and height if not specified.
1228
1362
  - `scrolling_area` `#scroll_to` 3rd and 4th arguments (`width` and `height`) default to main window width and height if not specified.
1229
- - `area` paths are specified declaratively with figures underneath (e.g. `rectangle`) and `area` draw listener is automatically generated
1363
+ - `area` paths are specified declaratively with shapes/figures underneath (e.g. `rectangle`), and `area` draw listener is automatically generated
1364
+ - `area` path shapes can be added directly under `area` without declaring `path` explicitly as a convenient shorthand
1230
1365
  - Observe figure properties (e.g. `rectangle` `width`) for changes and automatically redraw containing area accordingly
1231
1366
  - Observe `path` `fill` and `stroke` hashes for changes and automatically redraw containing area accordingly
1232
1367
  - Observe `text` and `string` properties for changes and automatically redraw containing area accordingly
@@ -1381,6 +1516,8 @@ Data-binding supports utilizing the [MVP (Model View Presenter)](https://en.wiki
1381
1516
 
1382
1517
  ![MVP](https://www.researchgate.net/profile/Gilles-Perrouin/publication/320249584/figure/fig8/AS:668260987068418@1536337243385/Model-view-presenter-architecture.png)
1383
1518
 
1519
+ #### Bidirectional (Two-Way) Data-Binding
1520
+
1384
1521
  [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
1522
  - `checkbox`: `checked`
1386
1523
  - `check_menu_item`: `checked`
@@ -1398,7 +1535,7 @@ Data-binding supports utilizing the [MVP (Model View Presenter)](https://en.wiki
1398
1535
  - `search_entry`: `text`
1399
1536
  - `slider`: `value`
1400
1537
  - `spinbox`: `value`
1401
- - `table`: `cell_rows` (explicit data-binding using `<=>` and [implicit data-binding](#table-api) by assigning value directly)
1538
+ - `table`: `cell_rows` (explicit data-binding by using `<=>` and [implicit data-binding](#table-api) by assigning value directly)
1402
1539
  - `time_picker`: `time`
1403
1540
 
1404
1541
  Example of bidirectional data-binding:
@@ -1421,6 +1558,162 @@ entry {
1421
1558
 
1422
1559
  That is data-binding `entered_text` attribute on `self` to `entry` `text` property and printing text after write to the model.
1423
1560
 
1561
+ ##### Table Data-Binding
1562
+
1563
+ One note about `table` `cell_rows` data-binding is that it works with either:
1564
+ - Raw data `Array` (rows) of `Array`s (column cells)
1565
+ - 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.
1566
+
1567
+ 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)):
1568
+
1569
+ ```ruby
1570
+ require 'glimmer-dsl-libui'
1571
+
1572
+ include Glimmer
1573
+
1574
+ data = [
1575
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],
1576
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],
1577
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],
1578
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],
1579
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
1580
+ ]
1581
+
1582
+ window('Contacts', 600, 600) {
1583
+ table {
1584
+ text_column('Name')
1585
+ text_column('Email')
1586
+ text_column('Phone')
1587
+ text_column('City')
1588
+ text_column('State')
1589
+
1590
+ cell_rows data
1591
+ }
1592
+ }.show
1593
+ ```
1594
+
1595
+ Example of `table` explicit data-binding of `cell_rows` to Model `Array` (you may copy/paste in [`girb`](#girb-glimmer-irb)):
1596
+
1597
+ ```ruby
1598
+ require 'glimmer-dsl-libui'
1599
+
1600
+ class SomeTable
1601
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
1602
+
1603
+ include Glimmer
1604
+
1605
+ attr_accessor :contacts
1606
+
1607
+ def initialize
1608
+ @contacts = [
1609
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
1610
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
1611
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
1612
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
1613
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
1614
+ ]
1615
+ end
1616
+
1617
+ def launch
1618
+ window('Contacts', 600, 200) {
1619
+ table {
1620
+ text_column('Name')
1621
+ text_column('Email')
1622
+ text_column('Phone')
1623
+ text_column('City')
1624
+ text_column('State')
1625
+
1626
+ cell_rows <=> [self, :contacts] # explicit data-binding to Model Array auto-inferring model attribute names from underscored table column names by convention
1627
+ }
1628
+ }.show
1629
+ end
1630
+ end
1631
+
1632
+ SomeTable.new.launch
1633
+ ```
1634
+
1635
+ 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)):
1636
+
1637
+ ```ruby
1638
+ require 'glimmer-dsl-libui'
1639
+
1640
+ class SomeTable
1641
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
1642
+
1643
+ include Glimmer
1644
+
1645
+ attr_accessor :contacts
1646
+
1647
+ def initialize
1648
+ @contacts = [
1649
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
1650
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
1651
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
1652
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
1653
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
1654
+ ]
1655
+ end
1656
+
1657
+ def launch
1658
+ window('Contacts', 600, 200) {
1659
+ table {
1660
+ text_column('Name')
1661
+ text_column('Email')
1662
+ text_column('Phone')
1663
+ text_column('City/Town')
1664
+ text_column('State/Province')
1665
+
1666
+ cell_rows <=> [self, :contacts, column_attributes: {'City/Town' => :city, 'State/Province' => :state}]
1667
+ }
1668
+ }.show
1669
+ end
1670
+ end
1671
+
1672
+ SomeTable.new.launch
1673
+ ```
1674
+
1675
+ 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)):
1676
+
1677
+ ```ruby
1678
+ require 'glimmer-dsl-libui'
1679
+
1680
+ class SomeTable
1681
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
1682
+
1683
+ include Glimmer
1684
+
1685
+ attr_accessor :contacts
1686
+
1687
+ def initialize
1688
+ @contacts = [
1689
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
1690
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
1691
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
1692
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
1693
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
1694
+ ]
1695
+ end
1696
+
1697
+ def launch
1698
+ window('Contacts', 600, 200) {
1699
+ table {
1700
+ text_column('Full Name')
1701
+ text_column('Email Address')
1702
+ text_column('Phone Number')
1703
+ text_column('City or Town')
1704
+ text_column('State or Province')
1705
+
1706
+ cell_rows <=> [self, :contacts, column_attributes: [:name, :email, :phone, :city, :state]]
1707
+ }
1708
+ }.show
1709
+ end
1710
+ end
1711
+
1712
+ SomeTable.new.launch
1713
+ ```
1714
+
1715
+ #### Unidirectional (One-Way) Data-Binding
1716
+
1424
1717
  [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).
1425
1718
 
1426
1719
  Example of unidirectional data-binding:
@@ -1443,6 +1736,8 @@ window {
1443
1736
 
1444
1737
  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`.
1445
1738
 
1739
+ #### Data-Binding API
1740
+
1446
1741
  To summarize the data-binding API:
1447
1742
  - `view_property <=> [model, attribute, *read_or_write_options]`: Bidirectional (two-way) data-binding to Model attribute accessor
1448
1743
  - `view_property <= [model, attribute, *read_only_options]`: Unidirectional (one-way) data-binding to Model attribute reader
@@ -1473,11 +1768,13 @@ entry {
1473
1768
  }
1474
1769
  ```
1475
1770
 
1476
- Data-binding gotchas:
1771
+ 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.
1772
+
1773
+ #### Data-Binding Gotchas
1774
+
1477
1775
  - 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)
1478
1776
  - 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.
1479
-
1480
- 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.
1777
+ - Data-binding a View control to another View control directly is not a good idea. Instead, data-bind both View controls to the same Presenter/Model attribute, and that keeps them in sync while keeping the code decoupled.
1481
1778
 
1482
1779
  ### API Gotchas
1483
1780
 
@@ -1490,272 +1787,12 @@ Learn more from data-binding usage in [Login](#login) (4 data-binding versions),
1490
1787
  - `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.
1491
1788
  - 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.
1492
1789
  - As per the code of [examples/basic_transform.rb](#basic-transform), Windows requires different ordering of transforms than Mac and Linux.
1790
+ - `scrolling_area#scroll_to` does not seem to work on Windows and Linux, but works fine on Mac
1493
1791
 
1494
1792
  ### Original API
1495
1793
 
1496
1794
  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):
1497
- - `alloc_control`
1498
- - `area_begin_user_window_move`
1499
- - `area_begin_user_window_resize`
1500
- - `area_queue_redraw_all`
1501
- - `area_scroll_to`
1502
- - `area_set_size`
1503
- - `attribute_color`
1504
- - `attribute_family`
1505
- - `attribute_features`
1506
- - `attribute_get_type`
1507
- - `attribute_italic`
1508
- - `attribute_size`
1509
- - `attribute_stretch`
1510
- - `attribute_underline`
1511
- - `attribute_underline_color`
1512
- - `attribute_weight`
1513
- - `attributed_string_append_unattributed`
1514
- - `attributed_string_byte_index_to_grapheme`
1515
- - `attributed_string_delete`
1516
- - `attributed_string_for_each_attribute`
1517
- - `attributed_string_grapheme_to_byte_index`
1518
- - `attributed_string_insert_at_unattributed`
1519
- - `attributed_string_len`
1520
- - `attributed_string_num_graphemes`
1521
- - `attributed_string_set_attribute`
1522
- - `attributed_string_string`
1523
- - `box_append`
1524
- - `box_delete`
1525
- - `box_padded`
1526
- - `box_set_padded`
1527
- - `button_on_clicked`
1528
- - `button_set_text`
1529
- - `button_text`
1530
- - `checkbox_checked`
1531
- - `checkbox_on_toggled`
1532
- - `checkbox_set_checked`
1533
- - `checkbox_set_text`
1534
- - `checkbox_text`
1535
- - `color_button_color`
1536
- - `color_button_on_changed`
1537
- - `color_button_set_color`
1538
- - `combobox_append`
1539
- - `combobox_on_selected`
1540
- - `combobox_selected`
1541
- - `combobox_set_selected`
1542
- - `control_destroy`
1543
- - `control_disable`
1544
- - `control_enable`
1545
- - `control_enabled`
1546
- - `control_enabled_to_user`
1547
- - `control_handle`
1548
- - `control_hide`
1549
- - `control_parent`
1550
- - `control_set_parent`
1551
- - `control_show`
1552
- - `control_toplevel`
1553
- - `control_verify_set_parent`
1554
- - `control_visible`
1555
- - `date_time_picker_on_changed`
1556
- - `date_time_picker_set_time`
1557
- - `date_time_picker_time`
1558
- - `draw_clip`
1559
- - `draw_fill`
1560
- - `draw_free_path`
1561
- - `draw_free_text_layout`
1562
- - `draw_matrix_invert`
1563
- - `draw_matrix_invertible`
1564
- - `draw_matrix_multiply`
1565
- - `draw_matrix_rotate`
1566
- - `draw_matrix_scale`
1567
- - `draw_matrix_set_identity`
1568
- - `draw_matrix_skew`
1569
- - `draw_matrix_transform_point`
1570
- - `draw_matrix_transform_size`
1571
- - `draw_matrix_translate`
1572
- - `draw_new_path`
1573
- - `draw_new_text_layout`
1574
- - `draw_path_add_rectangle`
1575
- - `draw_path_arc_to`
1576
- - `draw_path_bezier_to`
1577
- - `draw_path_close_figure`
1578
- - `draw_path_end`
1579
- - `draw_path_line_to`
1580
- - `draw_path_new_figure`
1581
- - `draw_path_new_figure_with_arc`
1582
- - `draw_restore`
1583
- - `draw_save`
1584
- - `draw_stroke`
1585
- - `draw_text`
1586
- - `draw_text_layout_extents`
1587
- - `draw_transform`
1588
- - `editable_combobox_append`
1589
- - `editable_combobox_on_changed`
1590
- - `editable_combobox_set_text`
1591
- - `editable_combobox_text`
1592
- - `entry_on_changed`
1593
- - `entry_read_only`
1594
- - `entry_set_read_only`
1595
- - `entry_set_text`
1596
- - `entry_text`
1597
- - `ffi_lib`
1598
- - `ffi_lib=`
1599
- - `font_button_font`
1600
- - `font_button_on_changed`
1601
- - `form_append`
1602
- - `form_delete`
1603
- - `form_padded`
1604
- - `form_set_padded`
1605
- - `free_attribute`
1606
- - `free_attributed_string`
1607
- - `free_control`
1608
- - `free_font_button_font`
1609
- - `free_image`
1610
- - `free_init_error`
1611
- - `free_open_type_features`
1612
- - `free_table_model`
1613
- - `free_table_value`
1614
- - `free_text`
1615
- - `grid_append`
1616
- - `grid_insert_at`
1617
- - `grid_padded`
1618
- - `grid_set_padded`
1619
- - `group_margined`
1620
- - `group_set_child`
1621
- - `group_set_margined`
1622
- - `group_set_title`
1623
- - `group_title`
1624
- - `image_append`
1625
- - `init`
1626
- - `label_set_text`
1627
- - `label_text`
1628
- - `main`
1629
- - `main_step`
1630
- - `main_steps`
1631
- - `menu_append_about_item`
1632
- - `menu_append_check_item`
1633
- - `menu_append_item`
1634
- - `menu_append_preferences_item`
1635
- - `menu_append_quit_item`
1636
- - `menu_append_separator`
1637
- - `menu_item_checked`
1638
- - `menu_item_disable`
1639
- - `menu_item_enable`
1640
- - `menu_item_on_clicked`
1641
- - `menu_item_set_checked`
1642
- - `msg_box`
1643
- - `msg_box_error`
1644
- - `multiline_entry_append`
1645
- - `multiline_entry_on_changed`
1646
- - `multiline_entry_read_only`
1647
- - `multiline_entry_set_read_only`
1648
- - `multiline_entry_set_text`
1649
- - `multiline_entry_text`
1650
- - `new_area`
1651
- - `new_attributed_string`
1652
- - `new_background_attribute`
1653
- - `new_button`
1654
- - `new_checkbox`
1655
- - `new_color_attribute`
1656
- - `new_color_button`
1657
- - `new_combobox`
1658
- - `new_date_picker`
1659
- - `new_date_time_picker`
1660
- - `new_editable_combobox`
1661
- - `new_entry`
1662
- - `new_family_attribute`
1663
- - `new_features_attribute`
1664
- - `new_font_button`
1665
- - `new_form`
1666
- - `new_grid`
1667
- - `new_group`
1668
- - `new_horizontal_box`
1669
- - `new_horizontal_separator`
1670
- - `new_image`
1671
- - `new_italic_attribute`
1672
- - `new_label`
1673
- - `new_menu`
1674
- - `new_multiline_entry`
1675
- - `new_non_wrapping_multiline_entry`
1676
- - `new_open_type_features`
1677
- - `new_password_entry`
1678
- - `new_progress_bar`
1679
- - `new_radio_buttons`
1680
- - `new_scrolling_area`
1681
- - `new_search_entry`
1682
- - `new_size_attribute`
1683
- - `new_slider`
1684
- - `new_spinbox`
1685
- - `new_stretch_attribute`
1686
- - `new_tab`
1687
- - `new_table`
1688
- - `new_table_model`
1689
- - `new_table_value_color`
1690
- - `new_table_value_image`
1691
- - `new_table_value_int`
1692
- - `new_table_value_string`
1693
- - `new_time_picker`
1694
- - `new_underline_attribute`
1695
- - `new_underline_color_attribute`
1696
- - `new_vertical_box`
1697
- - `new_vertical_separator`
1698
- - `new_weight_attribute`
1699
- - `new_window`
1700
- - `on_should_quit`
1701
- - `open_file`
1702
- - `open_type_features_add`
1703
- - `open_type_features_clone`
1704
- - `open_type_features_for_each`
1705
- - `open_type_features_get`
1706
- - `open_type_features_remove`
1707
- - `progress_bar_set_value`
1708
- - `progress_bar_value`
1709
- - `queue_main`
1710
- - `quit`
1711
- - `radio_buttons_append`
1712
- - `radio_buttons_on_selected`
1713
- - `radio_buttons_selected`
1714
- - `radio_buttons_set_selected`
1715
- - `save_file`
1716
- - `slider_on_changed`
1717
- - `slider_set_value`
1718
- - `slider_value`
1719
- - `spinbox_on_changed`
1720
- - `spinbox_set_value`
1721
- - `spinbox_value`
1722
- - `tab_append`
1723
- - `tab_delete`
1724
- - `tab_insert_at`
1725
- - `tab_margined`
1726
- - `tab_num_pages`
1727
- - `tab_set_margined`
1728
- - `table_append_button_column`
1729
- - `table_append_checkbox_column`
1730
- - `table_append_checkbox_text_column`
1731
- - `table_append_image_column`
1732
- - `table_append_image_text_column`
1733
- - `table_append_progress_bar_column`
1734
- - `table_append_text_column`
1735
- - `table_model_row_changed`
1736
- - `table_model_row_deleted`
1737
- - `table_model_row_inserted`
1738
- - `table_value_color`
1739
- - `table_value_get_type`
1740
- - `table_value_image`
1741
- - `table_value_int`
1742
- - `table_value_string`
1743
- - `timer`
1744
- - `uninit`
1745
- - `user_bug_cannot_set_parent_on_toplevel`
1746
- - `window_borderless`
1747
- - `window_content_size`
1748
- - `window_fullscreen`
1749
- - `window_margined`
1750
- - `window_on_closing`
1751
- - `window_on_content_size_changed`
1752
- - `window_set_borderless`
1753
- - `window_set_child`
1754
- - `window_set_content_size`
1755
- - `window_set_fullscreen`
1756
- - `window_set_margined`
1757
- - `window_set_title`
1758
- - `window_title`
1795
+ `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`
1759
1796
 
1760
1797
  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):
1761
1798
  - Check out [LibUI ffi.rb](https://github.com/kojix2/LibUI/blob/main/lib/libui/ffi.rb)
@@ -3064,7 +3101,44 @@ UI.main
3064
3101
  UI.quit
3065
3102
  ```
3066
3103
 
3067
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3104
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (passing file url as image):
3105
+
3106
+ ```ruby
3107
+ # frozen_string_literal: true
3108
+
3109
+ # NOTE:
3110
+ # This example displays images that can be freely downloaded from the Studio Ghibli website.
3111
+
3112
+ require 'glimmer-dsl-libui'
3113
+
3114
+ include Glimmer
3115
+
3116
+ IMAGE_ROWS = []
3117
+
3118
+ 50.times do |i|
3119
+ url = format('https://www.ghibli.jp/gallery/thumb-redturtle%03d.png', (i + 1))
3120
+ puts "Processing Image: #{url}"; $stdout.flush # for Windows
3121
+ IMAGE_ROWS << [url] # array of one column cell
3122
+ rescue StandardError => e
3123
+ warn url, e.message
3124
+ end
3125
+
3126
+ window('The Red Turtle', 310, 350, false) {
3127
+ horizontal_box {
3128
+ table {
3129
+ image_column('www.ghibli.jp/works/red-turtle')
3130
+
3131
+ cell_rows IMAGE_ROWS
3132
+ }
3133
+ }
3134
+
3135
+ on_closing do
3136
+ puts 'Bye Bye'
3137
+ end
3138
+ }.show
3139
+ ```
3140
+
3141
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (automatic construction of `image`):
3068
3142
 
3069
3143
  ```ruby
3070
3144
  # NOTE:
@@ -3099,7 +3173,7 @@ window('The Red Turtle', 310, 350, false) {
3099
3173
  }.show
3100
3174
  ```
3101
3175
 
3102
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (manual construction of `image` from `image_part`):
3176
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (manual construction of `image` from `image_part`):
3103
3177
 
3104
3178
  ```ruby
3105
3179
  # NOTE:
@@ -3167,7 +3241,44 @@ Mac | Windows | Linux
3167
3241
  ----|---------|------
3168
3242
  ![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)
3169
3243
 
3170
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3244
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (passing file url as image):
3245
+
3246
+ ```ruby
3247
+ # frozen_string_literal: true
3248
+
3249
+ # NOTE:
3250
+ # This example displays images that can be freely downloaded from the Studio Ghibli website.
3251
+
3252
+ require 'glimmer-dsl-libui'
3253
+
3254
+ include Glimmer
3255
+
3256
+ IMAGE_ROWS = []
3257
+
3258
+ 5.times do |i|
3259
+ url = format('https://www.ghibli.jp/gallery/thumb-redturtle%03d.png', (i + 1))
3260
+ puts "Processing Image: #{url}"; $stdout.flush # for Windows
3261
+ text = url.sub('https://www.ghibli.jp/gallery/thumb-redturtle', '').sub('.png', '')
3262
+ IMAGE_ROWS << [[url, text], [url, text]] # cell values are dual-element arrays
3263
+ rescue StandardError => e
3264
+ warn url, e.message
3265
+ end
3266
+
3267
+ window('The Red Turtle', 670, 350) {
3268
+ horizontal_box {
3269
+ table {
3270
+ image_text_column('image/number')
3271
+ image_text_column('image/number (editable)') {
3272
+ editable true
3273
+ }
3274
+
3275
+ cell_rows IMAGE_ROWS
3276
+ }
3277
+ }
3278
+ }.show
3279
+ ```
3280
+
3281
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (automatic construction of `image`):
3171
3282
 
3172
3283
  ```ruby
3173
3284
  # NOTE:
@@ -3272,22 +3383,28 @@ Mac | Windows | Linux
3272
3383
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
3273
3384
 
3274
3385
  ```ruby
3275
- # frozen_string_literal: true
3276
-
3277
3386
  require 'glimmer-dsl-libui'
3278
3387
 
3279
3388
  class BasicTableButton
3389
+ BasicAnimal = Struct.new(:name, :sound)
3390
+
3391
+ class Animal < BasicAnimal
3392
+ def action
3393
+ 'delete'
3394
+ end
3395
+ end
3396
+
3280
3397
  include Glimmer
3281
3398
 
3282
- attr_accessor :data
3399
+ attr_accessor :animals
3283
3400
 
3284
3401
  def initialize
3285
- @data = [
3286
- %w[cat meow delete],
3287
- %w[dog woof delete],
3288
- %w[chicken cock-a-doodle-doo delete],
3289
- %w[horse neigh delete],
3290
- %w[cow moo delete]
3402
+ @animals = [
3403
+ Animal.new('cat', 'meow'),
3404
+ Animal.new('dog', 'woof'),
3405
+ Animal.new('chicken', 'cock-a-doodle-doo'),
3406
+ Animal.new('horse', 'neigh'),
3407
+ Animal.new('cow', 'moo'),
3291
3408
  ]
3292
3409
  end
3293
3410
 
@@ -3300,17 +3417,19 @@ class BasicTableButton
3300
3417
  button_column('Action') {
3301
3418
  on_clicked do |row|
3302
3419
  # Option 1: direct data deletion is the simpler solution
3303
- # @data.delete_at(row) # automatically deletes actual table row due to explicit data-binding
3420
+ # @animals.delete_at(row) # automatically deletes actual table row due to explicit data-binding
3304
3421
 
3305
- # Option 2: cloning only to demonstrate table row deletion upon explicit setting of data attribute (cloning is not recommended beyond demonstrating this point)
3306
- new_data = @data.clone
3307
- new_data.delete_at(row)
3308
- self.data = new_data # automatically loses deleted table row due to explicit data-binding
3422
+ # Option 2: cloning only to demonstrate table row deletion upon explicit setting of animals attribute (cloning is not recommended beyond demonstrating this point)
3423
+ new_animals = @animals.clone
3424
+ new_animals.delete_at(row)
3425
+ self.animals = new_animals # automatically loses deleted table row due to explicit data-binding
3309
3426
  end
3310
3427
  }
3311
3428
 
3312
- cell_rows <=> [self, :data] # explicit data-binding of table cell_rows to self.data
3313
3429
 
3430
+ cell_rows <= [self, :animals, column_attributes: {'Animal' => :name, 'Description' => :sound}]
3431
+
3432
+ # explicit unidirectional data-binding of table cell_rows to self.animals
3314
3433
  on_changed do |row, type, row_data|
3315
3434
  puts "Row #{row} #{type}: #{row_data}"
3316
3435
  $stdout.flush
@@ -3533,16 +3652,126 @@ Mac | Windows | Linux
3533
3652
  ----|---------|------
3534
3653
  ![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)
3535
3654
 
3536
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3655
+ 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):
3537
3656
 
3538
3657
  ```ruby
3539
- # frozen_string_literal: true
3658
+ require 'glimmer-dsl-libui'
3540
3659
 
3660
+ class BasicTableColor
3661
+ Animal = Struct.new(:name, :sound, :mammal)
3662
+
3663
+ class AnimalPresenter < Animal
3664
+ def name_color
3665
+ color = case name
3666
+ when 'cat'
3667
+ :red
3668
+ when 'dog'
3669
+ :yellow
3670
+ when 'chicken'
3671
+ :beige
3672
+ when 'horse'
3673
+ :purple
3674
+ when 'cow'
3675
+ :gray
3676
+ end
3677
+ [name, color]
3678
+ end
3679
+
3680
+ def sound_color
3681
+ color = case name
3682
+ when 'cat', 'chicken', 'cow'
3683
+ :blue
3684
+ when 'dog', 'horse'
3685
+ {r: 240, g: 32, b: 32}
3686
+ end
3687
+ [sound, color]
3688
+ end
3689
+
3690
+ def mammal_description_color
3691
+ color = case name
3692
+ when 'cat', 'dog', 'horse', 'cow'
3693
+ :green
3694
+ when 'chicken'
3695
+ :red
3696
+ end
3697
+ [mammal, 'mammal', color]
3698
+ end
3699
+
3700
+ def image_description_color
3701
+ color = case name
3702
+ when 'cat', 'dog', 'horse'
3703
+ :dark_blue
3704
+ when 'chicken'
3705
+ :beige
3706
+ when 'cow'
3707
+ :brown
3708
+ end
3709
+ [img, 'Glimmer', color]
3710
+ end
3711
+
3712
+ def img
3713
+ # scale image to 24x24 (can be passed as file path String only instead of Array to avoid scaling)
3714
+ [File.expand_path('../icons/glimmer.png', __dir__), 24, 24]
3715
+ end
3716
+
3717
+ def background_color
3718
+ case name
3719
+ when 'cat'
3720
+ {r: 255, g: 120, b: 0, a: 0.5}
3721
+ when 'dog'
3722
+ :skyblue
3723
+ when 'chicken'
3724
+ {r: 5, g: 120, b: 110}
3725
+ when 'horse'
3726
+ '#13a1fb'
3727
+ when 'cow'
3728
+ 0x12ff02
3729
+ end
3730
+ end
3731
+ end
3732
+
3733
+ include Glimmer
3734
+
3735
+ attr_accessor :animals
3736
+
3737
+ def initialize
3738
+ @animals = [
3739
+ AnimalPresenter.new('cat', 'meow', true),
3740
+ AnimalPresenter.new('dog', 'woof', true),
3741
+ AnimalPresenter.new('chicken', 'cock-a-doodle-doo', false),
3742
+ AnimalPresenter.new('horse', 'neigh', true),
3743
+ AnimalPresenter.new('cow', 'moo', true),
3744
+ ]
3745
+ end
3746
+
3747
+ def launch
3748
+ window('Animals', 500, 200) {
3749
+ horizontal_box {
3750
+ table {
3751
+ text_color_column('Animal')
3752
+ text_color_column('Sound')
3753
+ checkbox_text_color_column('Description')
3754
+ image_text_color_column('GUI')
3755
+ background_color_column # must always be the last column and always expects data-binding model attribute `background_color` when binding to Array of models
3756
+
3757
+ cell_rows <= [self, :animals, column_attributes: {'Animal' => :name_color, 'Sound' => :sound_color, 'Description' => :mammal_description_color, 'GUI' => :image_description_color}]
3758
+ }
3759
+ }
3760
+ }.show
3761
+ end
3762
+ end
3763
+
3764
+ BasicTableColor.new.launch
3765
+ ```
3766
+
3767
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (with implicit [data-binding](#data-binding) to raw data rows):
3768
+
3769
+ ```ruby
3541
3770
  require 'glimmer-dsl-libui'
3542
3771
 
3543
3772
  include Glimmer
3544
3773
 
3545
- img = image(File.expand_path('../icons/glimmer.png', __dir__), 24, 24)
3774
+ 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)
3546
3775
 
3547
3776
  data = [
3548
3777
  [['cat', :red] , ['meow', :blue] , [true, 'mammal', :green], [img, 'Glimmer', :dark_blue], {r: 255, g: 120, b: 0, a: 0.5}],
@@ -3559,7 +3788,7 @@ window('Animals', 500, 200) {
3559
3788
  text_color_column('Sound')
3560
3789
  checkbox_text_color_column('Description')
3561
3790
  image_text_color_column('GUI')
3562
- background_color_column('Mammal')
3791
+ background_color_column # must be the last column
3563
3792
 
3564
3793
  cell_rows data
3565
3794
  }
@@ -3567,7 +3796,7 @@ window('Animals', 500, 200) {
3567
3796
  }.show
3568
3797
  ```
3569
3798
 
3570
- 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`):
3799
+ 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`):
3571
3800
 
3572
3801
  ```ruby
3573
3802
  require 'glimmer-dsl-libui'
@@ -3601,7 +3830,7 @@ window('Animals', 500, 200) {
3601
3830
  text_color_column('Sound')
3602
3831
  checkbox_text_color_column('Description')
3603
3832
  image_text_color_column('GUI')
3604
- background_color_column('Mammal')
3833
+ background_color_column
3605
3834
 
3606
3835
  cell_rows data
3607
3836
  }
@@ -3743,9 +3972,9 @@ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/
3743
3972
  ruby -r glimmer-dsl-libui -e "require 'examples/basic_scrolling_area'"
3744
3973
  ```
3745
3974
 
3746
- Mac | Linux
3747
- ----|------
3748
- ![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)
3975
+ Mac | Windows | Linux
3976
+ ----|---------|------
3977
+ ![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)
3749
3978
 
3750
3979
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3751
3980
 
@@ -3832,6 +4061,8 @@ BasicScrollingArea.new.launch
3832
4061
 
3833
4062
  #### Basic Image
3834
4063
 
4064
+ Please note the caveats of [Area Image](#area-image) **(Alpha Feature)** with regards to this example.
4065
+
3835
4066
  [examples/basic_image.rb](examples/basic_image.rb)
3836
4067
 
3837
4068
  Run with this command from the root of the project if you cloned the project:
@@ -5082,9 +5313,9 @@ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/
5082
5313
  ruby -r glimmer-dsl-libui -e "require 'examples/button_counter'"
5083
5314
  ```
5084
5315
 
5085
- Mac | Linux
5086
- ----|------
5087
- ![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)
5316
+ Mac | Windows | Linux
5317
+ ----|---------|------
5318
+ ![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)
5088
5319
 
5089
5320
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
5090
5321
 
@@ -5748,6 +5979,71 @@ MAIN_WINDOW = window('Control Gallery', 600, 500) {
5748
5979
  MAIN_WINDOW.show
5749
5980
  ```
5750
5981
 
5982
+ #### CPU Percentage
5983
+
5984
+ This example shows CPU usage percentage second by second.
5985
+
5986
+ 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.
5987
+
5988
+ [examples/cpu_percentage.rb](examples/cpu_percentage.rb)
5989
+
5990
+ Run with this command from the root of the project if you cloned the project:
5991
+
5992
+ ```
5993
+ ruby -r './lib/glimmer-dsl-libui' examples/cpu_percentage.rb
5994
+ ```
5995
+
5996
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
5997
+
5998
+ ```
5999
+ ruby -r glimmer-dsl-libui -e "require 'examples/cpu_percentage'"
6000
+ ```
6001
+
6002
+ Mac | Windows | Linux
6003
+ ----|---------|------
6004
+ ![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)
6005
+
6006
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
6007
+
6008
+ ```ruby
6009
+ require 'glimmer-dsl-libui'
6010
+ require 'bigdecimal'
6011
+
6012
+ include Glimmer
6013
+
6014
+ data = [
6015
+ ['CPU', '0%', 0],
6016
+ ]
6017
+
6018
+ Glimmer::LibUI.timer(1) do
6019
+ cpu_percentage_value = nil
6020
+ if OS.windows?
6021
+ cpu_percentage_raw_value = `wmic cpu get loadpercentage`
6022
+ cpu_percentage_value = cpu_percentage_raw_value.split("\n")[2].to_i
6023
+ elsif OS.mac?
6024
+ cpu_percentage_value = `ps -A -o %cpu | awk '{s+=$1} END {print s}'`.to_i
6025
+ elsif OS.linux?
6026
+ stats = `top -n 1`
6027
+ idle_percentage = stats.split("\n")[2].match(/ni,.* (.*) .*id/)[1]
6028
+ cpu_percentage_value = (BigDecimal(100) - BigDecimal(idle_percentage)).to_i
6029
+ end
6030
+ data[0][1] = "#{cpu_percentage_value}%"
6031
+ data[0][2] = cpu_percentage_value
6032
+ end
6033
+
6034
+ window('CPU Percentage', 400, 50) {
6035
+ vertical_box {
6036
+ table {
6037
+ text_column('Name')
6038
+ text_column('Value')
6039
+ progress_bar_column('Percentage')
6040
+
6041
+ cell_rows data # implicit data-binding
6042
+ }
6043
+ }
6044
+ }.show
6045
+ ```
6046
+
5751
6047
  #### Custom Draw Text
5752
6048
 
5753
6049
  [examples/custom_draw_text.rb](examples/custom_draw_text.rb)
@@ -6458,8 +6754,8 @@ window('Editable animal sounds', 300, 200) {
6458
6754
  text_column('Animal')
6459
6755
  text_column('Description')
6460
6756
 
6461
- cell_rows data
6462
6757
  editable true
6758
+ cell_rows data
6463
6759
 
6464
6760
  on_changed do |row, type, row_data| # fires on all changes (even ones happening through data array)
6465
6761
  puts "Row #{row} #{type}: #{row_data}"
@@ -6474,30 +6770,382 @@ window('Editable animal sounds', 300, 200) {
6474
6770
  on_closing do
6475
6771
  puts 'Bye Bye'
6476
6772
  end
6477
- }.show
6478
- ```
6479
-
6480
- #### Form Table
6481
-
6482
- [examples/form_table.rb](examples/form_table.rb)
6483
-
6484
- Run with this command from the root of the project if you cloned the project:
6485
-
6486
- ```
6487
- ruby -r './lib/glimmer-dsl-libui' examples/form_table.rb
6488
- ```
6489
-
6490
- Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
6773
+ }.show
6774
+ ```
6775
+
6776
+ #### Form Table
6777
+
6778
+ [examples/form_table.rb](examples/form_table.rb)
6779
+
6780
+ Run with this command from the root of the project if you cloned the project:
6781
+
6782
+ ```
6783
+ ruby -r './lib/glimmer-dsl-libui' examples/form_table.rb
6784
+ ```
6785
+
6786
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
6787
+
6788
+ ```
6789
+ ruby -r glimmer-dsl-libui -e "require 'examples/form_table'"
6790
+ ```
6791
+
6792
+ Mac | Windows | Linux
6793
+ ----|---------|------
6794
+ ![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)
6795
+
6796
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
6797
+
6798
+ ```ruby
6799
+ require 'glimmer-dsl-libui'
6800
+
6801
+ class FormTable
6802
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
6803
+
6804
+ include Glimmer
6805
+
6806
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
6807
+
6808
+ def initialize
6809
+ @contacts = [
6810
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
6811
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
6812
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
6813
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
6814
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
6815
+ ]
6816
+ end
6817
+
6818
+ def launch
6819
+ window('Contacts', 600, 600) { |w|
6820
+ margined true
6821
+
6822
+ vertical_box {
6823
+ form {
6824
+ stretchy false
6825
+
6826
+ entry {
6827
+ label 'Name'
6828
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6829
+ }
6830
+
6831
+ entry {
6832
+ label 'Email'
6833
+ text <=> [self, :email]
6834
+ }
6835
+
6836
+ entry {
6837
+ label 'Phone'
6838
+ text <=> [self, :phone]
6839
+ }
6840
+
6841
+ entry {
6842
+ label 'City'
6843
+ text <=> [self, :city]
6844
+ }
6845
+
6846
+ entry {
6847
+ label 'State'
6848
+ text <=> [self, :state]
6849
+ }
6850
+ }
6851
+
6852
+ button('Save Contact') {
6853
+ stretchy false
6854
+
6855
+ on_clicked do
6856
+ new_row = [name, email, phone, city, state]
6857
+ if new_row.include?('')
6858
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6859
+ else
6860
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to explicit data-binding
6861
+ @unfiltered_contacts = @contacts.dup
6862
+ self.name = '' # automatically clears name entry through explicit data-binding
6863
+ self.email = ''
6864
+ self.phone = ''
6865
+ self.city = ''
6866
+ self.state = ''
6867
+ end
6868
+ end
6869
+ }
6870
+
6871
+ search_entry {
6872
+ stretchy false
6873
+ # bidirectional data-binding of text to self.filter_value with after_write option
6874
+ text <=> [self, :filter_value,
6875
+ after_write: ->(filter_value) { # execute after write to self.filter_value
6876
+ @unfiltered_contacts ||= @contacts.dup
6877
+ # Unfilter first to remove any previous filters
6878
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
6879
+ # Now, apply filter if entered
6880
+ unless filter_value.empty?
6881
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
6882
+ contact.members.any? do |attribute|
6883
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
6884
+ end
6885
+ end
6886
+ end
6887
+ }
6888
+ ]
6889
+ }
6890
+
6891
+ table {
6892
+ text_column('Name')
6893
+ text_column('Email')
6894
+ text_column('Phone')
6895
+ text_column('City')
6896
+ text_column('State')
6897
+
6898
+ editable true
6899
+ cell_rows <=> [self, :contacts] # explicit data-binding to Model Array auto-inferring model attribute names from underscored table column names by convention
6900
+
6901
+ on_changed do |row, type, row_data|
6902
+ puts "Row #{row} #{type}: #{row_data}"
6903
+ end
6904
+ }
6905
+ }
6906
+ }.show
6907
+ end
6908
+ end
6909
+
6910
+ FormTable.new.launch
6911
+ ```
6912
+
6913
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
6914
+
6915
+ ```ruby
6916
+ require 'glimmer-dsl-libui'
6917
+
6918
+ class FormTable
6919
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
6920
+
6921
+ include Glimmer
6922
+
6923
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
6924
+
6925
+ def initialize
6926
+ @contacts = [
6927
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
6928
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
6929
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
6930
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
6931
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
6932
+ ]
6933
+ end
6934
+
6935
+ def launch
6936
+ window('Contacts', 600, 600) { |w|
6937
+ margined true
6938
+
6939
+ vertical_box {
6940
+ form {
6941
+ stretchy false
6942
+
6943
+ entry {
6944
+ label 'Name'
6945
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6946
+ }
6947
+
6948
+ entry {
6949
+ label 'Email'
6950
+ text <=> [self, :email]
6951
+ }
6952
+
6953
+ entry {
6954
+ label 'Phone'
6955
+ text <=> [self, :phone]
6956
+ }
6957
+
6958
+ entry {
6959
+ label 'City'
6960
+ text <=> [self, :city]
6961
+ }
6962
+
6963
+ entry {
6964
+ label 'State'
6965
+ text <=> [self, :state]
6966
+ }
6967
+ }
6968
+
6969
+ button('Save Contact') {
6970
+ stretchy false
6971
+
6972
+ on_clicked do
6973
+ new_row = [name, email, phone, city, state]
6974
+ if new_row.include?('')
6975
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6976
+ else
6977
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to implicit data-binding
6978
+ @unfiltered_contacts = @contacts.dup
6979
+ self.name = '' # automatically clears name entry through explicit data-binding
6980
+ self.email = ''
6981
+ self.phone = ''
6982
+ self.city = ''
6983
+ self.state = ''
6984
+ end
6985
+ end
6986
+ }
6987
+
6988
+ search_entry {
6989
+ stretchy false
6990
+ # bidirectional data-binding of text to self.filter_value with after_write option
6991
+ text <=> [self, :filter_value,
6992
+ after_write: ->(filter_value) { # execute after write to self.filter_value
6993
+ @unfiltered_contacts ||= @contacts.dup
6994
+ # Unfilter first to remove any previous filters
6995
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
6996
+ # Now, apply filter if entered
6997
+ unless filter_value.empty?
6998
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
6999
+ contact.members.any? do |attribute|
7000
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
7001
+ end
7002
+ end
7003
+ end
7004
+ }
7005
+ ]
7006
+ }
7007
+
7008
+ table {
7009
+ text_column('Name')
7010
+ text_column('Email')
7011
+ text_column('Phone')
7012
+ text_column('City')
7013
+ text_column('State/Province')
7014
+
7015
+ editable true
7016
+ cell_rows <=> [self, :contacts, column_attributes: {'State/Province' => :state}] # explicit data-binding to Model Array with column_attributes mapping for a specific column
7017
+
7018
+ on_changed do |row, type, row_data|
7019
+ puts "Row #{row} #{type}: #{row_data}"
7020
+ end
7021
+ }
7022
+ }
7023
+ }.show
7024
+ end
7025
+ end
7026
+
7027
+ FormTable.new.launch
7028
+ ```
7029
+
7030
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
7031
+
7032
+ ```ruby
7033
+
7034
+ require 'glimmer-dsl-libui'
7035
+
7036
+ class FormTable
7037
+ Contact = Struct.new(:full_name, :email_address, :phone_number, :city_or_town, :state_or_province)
7038
+
7039
+ include Glimmer
7040
+
7041
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
7042
+
7043
+ def initialize
7044
+ @contacts = [
7045
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
7046
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
7047
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
7048
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
7049
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
7050
+ ]
7051
+ end
7052
+
7053
+ def launch
7054
+ window('Contacts', 600, 600) { |w|
7055
+ margined true
7056
+
7057
+ vertical_box {
7058
+ form {
7059
+ stretchy false
7060
+
7061
+ entry {
7062
+ label 'Name'
7063
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
7064
+ }
7065
+
7066
+ entry {
7067
+ label 'Email'
7068
+ text <=> [self, :email]
7069
+ }
7070
+
7071
+ entry {
7072
+ label 'Phone'
7073
+ text <=> [self, :phone]
7074
+ }
7075
+
7076
+ entry {
7077
+ label 'City'
7078
+ text <=> [self, :city]
7079
+ }
7080
+
7081
+ entry {
7082
+ label 'State'
7083
+ text <=> [self, :state]
7084
+ }
7085
+ }
7086
+
7087
+ button('Save Contact') {
7088
+ stretchy false
7089
+
7090
+ on_clicked do
7091
+ new_row = [name, email, phone, city, state]
7092
+ if new_row.include?('')
7093
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
7094
+ else
7095
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to implicit data-binding
7096
+ @unfiltered_contacts = @contacts.dup
7097
+ self.name = '' # automatically clears name entry through explicit data-binding
7098
+ self.email = ''
7099
+ self.phone = ''
7100
+ self.city = ''
7101
+ self.state = ''
7102
+ end
7103
+ end
7104
+ }
7105
+
7106
+ search_entry {
7107
+ stretchy false
7108
+ # bidirectional data-binding of text to self.filter_value with after_write option
7109
+ text <=> [self, :filter_value,
7110
+ after_write: ->(filter_value) { # execute after write to self.filter_value
7111
+ @unfiltered_contacts ||= @contacts.dup
7112
+ # Unfilter first to remove any previous filters
7113
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
7114
+ # Now, apply filter if entered
7115
+ unless filter_value.empty?
7116
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
7117
+ contact.members.any? do |attribute|
7118
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
7119
+ end
7120
+ end
7121
+ end
7122
+ }
7123
+ ]
7124
+ }
7125
+
7126
+ table {
7127
+ text_column('Name')
7128
+ text_column('Email')
7129
+ text_column('Phone')
7130
+ text_column('City')
7131
+ text_column('State')
7132
+
7133
+ editable true
7134
+ 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
7135
+
7136
+ on_changed do |row, type, row_data|
7137
+ puts "Row #{row} #{type}: #{row_data}"
7138
+ end
7139
+ }
7140
+ }
7141
+ }.show
7142
+ end
7143
+ end
6491
7144
 
7145
+ FormTable.new.launch
6492
7146
  ```
6493
- ruby -r glimmer-dsl-libui -e "require 'examples/form_table'"
6494
- ```
6495
-
6496
- Mac | Windows | Linux
6497
- ----|---------|------
6498
- ![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)
6499
7147
 
6500
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
7148
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (with explicit [data-binding](#data-binding) to raw data):
6501
7149
 
6502
7150
  ```ruby
6503
7151
  require 'glimmer-dsl-libui'
@@ -6509,11 +7157,11 @@ class FormTable
6509
7157
 
6510
7158
  def initialize
6511
7159
  @data = [
6512
- ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
6513
- ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
6514
- ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
6515
- ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
6516
- ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
7160
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],
7161
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],
7162
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],
7163
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],
7164
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
6517
7165
  ]
6518
7166
  end
6519
7167
 
@@ -6527,7 +7175,7 @@ class FormTable
6527
7175
 
6528
7176
  entry {
6529
7177
  label 'Name'
6530
- text <=> [self, :name]
7178
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6531
7179
  }
6532
7180
 
6533
7181
  entry {
@@ -6559,8 +7207,8 @@ class FormTable
6559
7207
  if new_row.include?('')
6560
7208
  msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6561
7209
  else
6562
- @data << new_row # automatically inserts a row into the table due to implicit data-binding
6563
- @unfiltered_data = @data.dup
7210
+ data << new_row # automatically inserts a row into the table due to implicit data-binding
7211
+ @unfiltered_data = data.dup
6564
7212
  self.name = '' # automatically clears name entry through explicit data-binding
6565
7213
  self.email = ''
6566
7214
  self.phone = ''
@@ -6572,14 +7220,15 @@ class FormTable
6572
7220
 
6573
7221
  search_entry {
6574
7222
  stretchy false
6575
- text <=> [self, :filter_value, # bidirectional data-binding of text to self.filter_value with after_write option
7223
+ # bidirectional data-binding of text to self.filter_value with after_write option
7224
+ text <=> [self, :filter_value,
6576
7225
  after_write: ->(filter_value) { # execute after write to self.filter_value
6577
- @unfiltered_data ||= @data.dup
7226
+ @unfiltered_data ||= data.dup
6578
7227
  # Unfilter first to remove any previous filters
6579
- self.data = @unfiltered_data # affects table indirectly through explicit data-binding
7228
+ data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
6580
7229
  # Now, apply filter if entered
6581
7230
  unless filter_value.empty?
6582
- self.data = @data.filter do |row_data| # affects table indirectly through explicit data-binding
7231
+ data.filter! do |row_data| # affects table indirectly through implicit data-binding
6583
7232
  row_data.any? do |cell|
6584
7233
  cell.to_s.downcase.include?(filter_value.downcase)
6585
7234
  end
@@ -6595,8 +7244,9 @@ class FormTable
6595
7244
  text_column('Phone')
6596
7245
  text_column('City')
6597
7246
  text_column('State')
6598
-
6599
- cell_rows <=> [self, :data] # explicit data-binding
7247
+
7248
+ editable true
7249
+ cell_rows <=> [self, :data] # explicit data-binding to raw data Array of Arrays
6600
7250
 
6601
7251
  on_changed do |row, type, row_data|
6602
7252
  puts "Row #{row} #{type}: #{row_data}"
@@ -6610,7 +7260,7 @@ end
6610
7260
  FormTable.new.launch
6611
7261
  ```
6612
7262
 
6613
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
7263
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 5 (with implicit [data-binding](#data-binding)):
6614
7264
 
6615
7265
  ```ruby
6616
7266
  require 'glimmer-dsl-libui'
@@ -6618,11 +7268,11 @@ require 'glimmer-dsl-libui'
6618
7268
  include Glimmer
6619
7269
 
6620
7270
  data = [
6621
- ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
6622
- ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
6623
- ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
6624
- ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
6625
- ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
7271
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],
7272
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],
7273
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],
7274
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],
7275
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
6626
7276
  ]
6627
7277
 
6628
7278
  window('Contacts', 600, 600) { |w|
@@ -6698,7 +7348,8 @@ window('Contacts', 600, 600) { |w|
6698
7348
  text_column('City')
6699
7349
  text_column('State')
6700
7350
 
6701
- cell_rows data # implicit data-binding
7351
+ editable true
7352
+ cell_rows data # implicit data-binding to raw data Array of Arrays
6702
7353
 
6703
7354
  on_changed do |row, type, row_data|
6704
7355
  puts "Row #{row} #{type}: #{row_data}"
@@ -8234,7 +8885,7 @@ Mac | Windows | Linux
8234
8885
  ----|---------|------
8235
8886
  ![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)
8236
8887
 
8237
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
8888
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
8238
8889
 
8239
8890
  ```ruby
8240
8891
  require 'glimmer-dsl-libui'
@@ -8326,6 +8977,108 @@ end
8326
8977
  Snake.new.launch
8327
8978
  ```
8328
8979
 
8980
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
8981
+
8982
+ ```ruby
8983
+ require 'glimmer-dsl-libui'
8984
+
8985
+ require_relative 'snake/presenter/grid'
8986
+
8987
+ class Snake
8988
+ include Glimmer
8989
+
8990
+ CELL_SIZE = 15
8991
+ SNAKE_MOVE_DELAY = 0.1
8992
+
8993
+ def initialize
8994
+ @game = Model::Game.new
8995
+ @grid = Presenter::Grid.new(@game)
8996
+ @game.start
8997
+ @keypress_queue = []
8998
+ create_gui
8999
+ register_observers
9000
+ end
9001
+
9002
+ def launch
9003
+ @main_window.show
9004
+ end
9005
+
9006
+ def register_observers
9007
+ @game.height.times do |row|
9008
+ @game.width.times do |column|
9009
+ observe(@grid.cells[row][column], :color) do |new_color|
9010
+ @cell_grid[row][column].fill = new_color
9011
+ end
9012
+ end
9013
+ end
9014
+
9015
+ observe(@game, :over) do |game_over|
9016
+ Glimmer::LibUI.queue_main do
9017
+ if game_over
9018
+ msg_box('Game Over!', "Score: #{@game.score} | High Score: #{@game.high_score}")
9019
+ @game.start
9020
+ end
9021
+ end
9022
+ end
9023
+
9024
+ Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
9025
+ unless @game.over?
9026
+ process_queued_keypress
9027
+ @game.snake.move
9028
+ end
9029
+ end
9030
+ end
9031
+
9032
+ def process_queued_keypress
9033
+ # 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)
9034
+ key = @keypress_queue.shift
9035
+ case [@game.snake.head.orientation, key]
9036
+ in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
9037
+ @game.snake.turn_right
9038
+ in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
9039
+ @game.snake.turn_left
9040
+ else
9041
+ # No Op
9042
+ end
9043
+ end
9044
+
9045
+ def create_gui
9046
+ @cell_grid = []
9047
+ @main_window = window {
9048
+ # data-bind window title to game score, converting it to a title string on read from the model
9049
+ title <= [@game, :score, on_read: -> (score) {"Snake (Score: #{@game.score})"}]
9050
+ content_size @game.width * CELL_SIZE, @game.height * CELL_SIZE
9051
+ resizable false
9052
+
9053
+ vertical_box {
9054
+ padded false
9055
+
9056
+ @game.height.times do |row|
9057
+ @cell_grid << []
9058
+ horizontal_box {
9059
+ padded false
9060
+
9061
+ @game.width.times do |column|
9062
+ area {
9063
+ @cell_grid.last << square(0, 0, CELL_SIZE) {
9064
+ fill Presenter::Cell::COLOR_CLEAR
9065
+ }
9066
+
9067
+ on_key_up do |area_key_event|
9068
+ @keypress_queue << area_key_event[:ext_key]
9069
+ end
9070
+ }
9071
+ end
9072
+ }
9073
+ end
9074
+ }
9075
+ }
9076
+ end
9077
+ end
9078
+
9079
+ Snake.new.launch
9080
+ ```
9081
+
8329
9082
  #### Tetris
8330
9083
 
8331
9084
  Glimmer Tetris utilizes many small areas to represent Tetromino blocks because this ensures smaller redraws per tetromino block color change, thus achieving higher performance than redrawing one large area on every little change.
@@ -8741,7 +9494,7 @@ Mac | Windows | Linux
8741
9494
  ----|---------|------
8742
9495
  ![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)
8743
9496
 
8744
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
9497
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
8745
9498
 
8746
9499
  ```ruby
8747
9500
  require 'glimmer-dsl-libui'
@@ -8787,6 +9540,7 @@ class TicTacToe
8787
9540
  text(23, 19) {
8788
9541
  string {
8789
9542
  font family: 'Arial', size: OS.mac? ? 20 : 16
9543
+ # data-bind string property of area text attributed string to tic tac toe board cell sign
8790
9544
  string <= [@tic_tac_toe_board[row + 1, column + 1], :sign] # board model is 1-based
8791
9545
  }
8792
9546
  }
@@ -8820,6 +9574,95 @@ end
8820
9574
  TicTacToe.new.launch
8821
9575
  ```
8822
9576
 
9577
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
9578
+
9579
+ ```ruby
9580
+
9581
+ require 'glimmer-dsl-libui'
9582
+
9583
+ require_relative "tic_tac_toe/board"
9584
+
9585
+ class TicTacToe
9586
+ include Glimmer
9587
+
9588
+ def initialize
9589
+ @tic_tac_toe_board = Board.new
9590
+ end
9591
+
9592
+ def launch
9593
+ create_gui
9594
+ register_observers
9595
+ @main_window.show
9596
+ end
9597
+
9598
+ def register_observers
9599
+ observe(@tic_tac_toe_board, :game_status) do |game_status|
9600
+ display_win_message if game_status == Board::WIN
9601
+ display_draw_message if game_status == Board::DRAW
9602
+ end
9603
+
9604
+ 3.times.map do |row|
9605
+ 3.times.map do |column|
9606
+ observe(@tic_tac_toe_board[row + 1, column + 1], :sign) do |sign| # board model is 1-based
9607
+ @cells[row][column].string = sign
9608
+ end
9609
+ end
9610
+ end
9611
+ end
9612
+
9613
+ def create_gui
9614
+ @main_window = window('Tic-Tac-Toe', 180, 180) {
9615
+ resizable false
9616
+
9617
+ @cells = []
9618
+ vertical_box {
9619
+ padded false
9620
+
9621
+ 3.times.map do |row|
9622
+ @cells << []
9623
+ horizontal_box {
9624
+ padded false
9625
+
9626
+ 3.times.map do |column|
9627
+ area {
9628
+ square(0, 0, 60) {
9629
+ stroke :black, thickness: 2
9630
+ }
9631
+ text(23, 19) {
9632
+ @cells[row] << string('') {
9633
+ font family: 'Arial', size: OS.mac? ? 20 : 16
9634
+ }
9635
+ }
9636
+ on_mouse_up do
9637
+ @tic_tac_toe_board.mark(row + 1, column + 1) # board model is 1-based
9638
+ end
9639
+ }
9640
+ end
9641
+ }
9642
+ end
9643
+ }
9644
+ }
9645
+ end
9646
+
9647
+ def display_win_message
9648
+ display_game_over_message("Player #{@tic_tac_toe_board.winning_sign} has won!")
9649
+ end
9650
+
9651
+ def display_draw_message
9652
+ display_game_over_message("Draw!")
9653
+ end
9654
+
9655
+ def display_game_over_message(message_text)
9656
+ Glimmer::LibUI.queue_main do
9657
+ msg_box('Game Over', message_text)
9658
+ @tic_tac_toe_board.reset!
9659
+ end
9660
+ end
9661
+ end
9662
+
9663
+ TicTacToe.new.launch
9664
+ ```
9665
+
8823
9666
  #### Timer
8824
9667
 
8825
9668
  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).