glimmer-dsl-libui 0.4.7 → 0.4.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/README.md +1043 -493
  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 +1 -1
  8. data/examples/button_counter.rb +2 -1
  9. data/examples/cpu_percentage.rb +36 -0
  10. data/examples/editable_table.rb +1 -1
  11. data/examples/form_table.rb +21 -17
  12. data/examples/form_table2.rb +104 -85
  13. data/examples/form_table3.rb +113 -0
  14. data/examples/form_table4.rb +110 -0
  15. data/examples/form_table5.rb +94 -0
  16. data/examples/meta_example.rb +21 -8
  17. data/examples/midi_player.rb +1 -1
  18. data/examples/snake.rb +19 -10
  19. data/examples/snake2.rb +97 -0
  20. data/examples/tetris.rb +15 -18
  21. data/examples/tic_tac_toe.rb +1 -0
  22. data/examples/tic_tac_toe2.rb +84 -0
  23. data/glimmer-dsl-libui.gemspec +0 -0
  24. data/lib/glimmer/dsl/libui/control_expression.rb +2 -1
  25. data/lib/glimmer/dsl/libui/shape_expression.rb +2 -2
  26. data/lib/glimmer/dsl/libui/string_expression.rb +2 -1
  27. data/lib/glimmer/libui/attributed_string.rb +3 -2
  28. data/lib/glimmer/libui/control_proxy/checkbox_proxy.rb +4 -0
  29. data/lib/glimmer/libui/control_proxy/color_button_proxy.rb +1 -2
  30. data/lib/glimmer/libui/control_proxy/combobox_proxy.rb +1 -2
  31. data/lib/glimmer/libui/control_proxy/date_time_picker_proxy.rb +1 -2
  32. data/lib/glimmer/libui/control_proxy/editable_combobox_proxy.rb +4 -5
  33. data/lib/glimmer/libui/control_proxy/entry_proxy.rb +1 -2
  34. data/lib/glimmer/libui/control_proxy/font_button_proxy.rb +5 -1
  35. data/lib/glimmer/libui/control_proxy/menu_item_proxy/check_menu_item_proxy.rb +4 -0
  36. data/lib/glimmer/libui/control_proxy/menu_item_proxy/radio_menu_item_proxy.rb +16 -4
  37. data/lib/glimmer/libui/control_proxy/multiline_entry_proxy.rb +1 -2
  38. data/lib/glimmer/libui/control_proxy/radio_buttons_proxy.rb +19 -0
  39. data/lib/glimmer/libui/control_proxy/slider_proxy.rb +1 -2
  40. data/lib/glimmer/libui/control_proxy/spinbox_proxy.rb +1 -2
  41. data/lib/glimmer/libui/control_proxy/table_proxy.rb +88 -24
  42. data/lib/glimmer/libui/control_proxy.rb +6 -4
  43. data/lib/glimmer/libui/data_bindable.rb +34 -4
  44. data/lib/glimmer/libui/shape.rb +3 -2
  45. data/lib/glimmer-dsl-libui.rb +1 -0
  46. metadata +9 -2
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.4.7
1
+ # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.4.11
2
2
  ## Prerequisite-Free Ruby Desktop Development GUI Library
3
3
  [![Gem Version](https://badge.fury.io/rb/glimmer-dsl-libui.svg)](http://badge.fury.io/rb/glimmer-dsl-libui)
4
4
  [![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
@@ -77,6 +77,127 @@ Mac | Windows | Linux
77
77
  ----|---------|------
78
78
  ![glimmer-dsl-libui-mac-basic-table-progress-bar.png](images/glimmer-dsl-libui-mac-basic-table-progress-bar.png) | ![glimmer-dsl-libui-windows-basic-table-progress-bar.png](images/glimmer-dsl-libui-windows-basic-table-progress-bar.png) | ![glimmer-dsl-libui-linux-basic-table-progress-bar.png](images/glimmer-dsl-libui-linux-basic-table-progress-bar.png)
79
79
 
80
+ Form Table
81
+
82
+ ```ruby
83
+ require 'glimmer-dsl-libui'
84
+
85
+ class FormTable
86
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
87
+
88
+ include Glimmer
89
+
90
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
91
+
92
+ def initialize
93
+ @contacts = [
94
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
95
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
96
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
97
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
98
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
99
+ ]
100
+ end
101
+
102
+ def launch
103
+ window('Contacts', 600, 600) { |w|
104
+ margined true
105
+
106
+ vertical_box {
107
+ form {
108
+ stretchy false
109
+
110
+ entry {
111
+ label 'Name'
112
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
113
+ }
114
+
115
+ entry {
116
+ label 'Email'
117
+ text <=> [self, :email]
118
+ }
119
+
120
+ entry {
121
+ label 'Phone'
122
+ text <=> [self, :phone]
123
+ }
124
+
125
+ entry {
126
+ label 'City'
127
+ text <=> [self, :city]
128
+ }
129
+
130
+ entry {
131
+ label 'State'
132
+ text <=> [self, :state]
133
+ }
134
+ }
135
+
136
+ button('Save Contact') {
137
+ stretchy false
138
+
139
+ on_clicked do
140
+ new_row = [name, email, phone, city, state]
141
+ if new_row.include?('')
142
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
143
+ else
144
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to explicit data-binding
145
+ @unfiltered_contacts = @contacts.dup
146
+ self.name = '' # automatically clears name entry through explicit data-binding
147
+ self.email = ''
148
+ self.phone = ''
149
+ self.city = ''
150
+ self.state = ''
151
+ end
152
+ end
153
+ }
154
+
155
+ search_entry {
156
+ stretchy false
157
+ # bidirectional data-binding of text to self.filter_value with after_write option
158
+ text <=> [self, :filter_value,
159
+ after_write: ->(filter_value) { # execute after write to self.filter_value
160
+ @unfiltered_contacts ||= @contacts.dup
161
+ # Unfilter first to remove any previous filters
162
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
163
+ # Now, apply filter if entered
164
+ unless filter_value.empty?
165
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
166
+ contact.members.any? do |attribute|
167
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
168
+ end
169
+ end
170
+ end
171
+ }
172
+ ]
173
+ }
174
+
175
+ table {
176
+ text_column('Name')
177
+ text_column('Email')
178
+ text_column('Phone')
179
+ text_column('City')
180
+ text_column('State')
181
+
182
+ editable true
183
+ cell_rows <=> [self, :contacts] # explicit data-binding to Model Array
184
+
185
+ on_changed do |row, type, row_data|
186
+ puts "Row #{row} #{type}: #{row_data}"
187
+ end
188
+ }
189
+ }
190
+ }.show
191
+ end
192
+ end
193
+
194
+ FormTable.new.launch
195
+ ```
196
+
197
+ Mac | Windows | Linux
198
+ ----|---------|------
199
+ ![glimmer-dsl-libui-mac-form-table.png](images/glimmer-dsl-libui-mac-form-table.png) | ![glimmer-dsl-libui-windows-form-table.png](images/glimmer-dsl-libui-windows-form-table.png) | ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png)
200
+
80
201
  Area Gallery
81
202
 
82
203
  ```ruby
@@ -267,6 +388,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
267
388
  - [Button Counter](#button-counter)
268
389
  - [Color The Circles](#color-the-circles)
269
390
  - [Control Gallery](#control-gallery)
391
+ - [CPU Percentage](#cpu-percentage)
270
392
  - [Custom Draw Text](#custom-draw-text)
271
393
  - [Dynamic Area](#dynamic-area)
272
394
  - [Editable Column Table](#editable-column-table)
@@ -373,10 +495,20 @@ gem install glimmer-dsl-libui
373
495
  Or install via Bundler `Gemfile`:
374
496
 
375
497
  ```ruby
376
- gem 'glimmer-dsl-libui', '~> 0.4.7'
498
+ gem 'glimmer-dsl-libui', '~> 0.4.11'
499
+ ```
500
+
501
+ Test that installation worked by running the [Meta-Example](#examples):
502
+
503
+ ```
504
+ ruby -r glimmer-dsl-libui -e "require 'examples/meta_example'"
377
505
  ```
378
506
 
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.
507
+ Mac | Windows | Linux
508
+ ----|---------|------
509
+ ![glimmer-dsl-libui-mac-meta-example.png](images/glimmer-dsl-libui-mac-meta-example.png) | ![glimmer-dsl-libui-windows-meta-example.png](images/glimmer-dsl-libui-windows-meta-example.png) | ![glimmer-dsl-libui-linux-meta-example.png](images/glimmer-dsl-libui-linux-meta-example.png)
510
+
511
+ Now to use [glimmer-dsl-libui](https://rubygems.org/gems/glimmer-dsl-libui), add `require 'glimmer-dsl-libui'` at the top, and then `include Glimmer` into the top-level main object for testing or into an actual class for serious usage.
380
512
 
381
513
  Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
382
514
 
@@ -451,7 +583,7 @@ Keyword(Args) | Properties | Listeners
451
583
  `about_menu_item` | None | `on_clicked`
452
584
  `area` | `auto_draw_enabled` | `on_draw(area_draw_params)`, `on_mouse_event(area_mouse_event)`, `on_mouse_down(area_mouse_event)`, `on_mouse_up(area_mouse_event)`, `on_mouse_drag_started(area_mouse_event)`, `on_mouse_dragged(area_mouse_event)`, `on_mouse_dropped(area_mouse_event)`, `on_mouse_entered`, `on_mouse_exited`, `on_key_event(area_key_event)`, `on_key_down(area_key_event)`, `on_key_up(area_key_event)`
453
585
  `arc(x_center as Numeric, y_center as Numeric, radius as Numeric, start_angle as Numeric, sweep as Numeric, is_negative as Boolean)` | `x_center` (`Numeric`), `y_center` (`Numeric`), `radius` (`Numeric`), `start_angle` (`Numeric`), `sweep` (`Numeric`), `is_negative` (Boolean) | None
454
- `background_color_column(name as String)` | None | None
586
+ `background_color_column` | None | None
455
587
  `bezier(c1_x as Numeric, c1_y as Numeric, c2_x as Numeric, c2_y as Numeric, end_x as Numeric, end_y as Numeric)` | `c1_x` (`Numeric`), `c1_y` (`Numeric`), `c2_x` (`Numeric`), `c2_y` (`Numeric`), `end_x` (`Numeric`), `end_y` (`Numeric`) | None
456
588
  `button(text as String)` | `text` (`String`) | `on_clicked`
457
589
  `button_column(name as String)` | `enabled` (Boolean) | None
@@ -615,154 +747,142 @@ Note that the `cell_rows` property declaration results in "implicit data-binding
615
747
  - Inserting cell rows: Calling `Array#<<`, `Array#push`, `Array#prepend`, or any insertion/addition `Array` method automatically inserts rows in actual `table` control
616
748
  - Changing cell rows: Calling `Array#[]=`, `Array#map!`, or any update `Array` method automatically updates rows in actual `table` control
617
749
 
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.
750
+ ([explicit data-binding](#data-binding) supports everything available with implicit data-binding too)
742
751
 
743
- Here is an example of a declarative `area` with a stable path (you may copy/paste in [`girb`](#girb-glimmer-irb)):
752
+ Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
744
753
 
745
754
  ```ruby
746
755
  require 'glimmer-dsl-libui'
747
756
 
748
757
  include Glimmer
749
758
 
750
- window('Basic Area', 400, 400) {
759
+ data = [
760
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],
761
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],
762
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],
763
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],
764
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
765
+ ]
766
+
767
+ window('Contacts', 600, 600) { |w|
751
768
  margined true
752
769
 
753
770
  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
771
+ form {
772
+ stretchy false
773
+
774
+ @name_entry = entry {
775
+ label 'Name'
776
+ }
777
+
778
+ @email_entry = entry {
779
+ label 'Email'
780
+ }
781
+
782
+ @phone_entry = entry {
783
+ label 'Phone'
784
+ }
785
+
786
+ @city_entry = entry {
787
+ label 'City'
788
+ }
789
+
790
+ @state_entry = entry {
791
+ label 'State'
759
792
  }
760
793
  }
761
- }
762
- }.show
763
- ```
764
-
765
- Mac | Windows | Linux
794
+
795
+ button('Save Contact') {
796
+ stretchy false
797
+
798
+ on_clicked do
799
+ new_row = [@name_entry.text, @email_entry.text, @phone_entry.text, @city_entry.text, @state_entry.text]
800
+ if new_row.include?('')
801
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
802
+ else
803
+ data << new_row # automatically inserts a row into the table due to implicit data-binding
804
+ @unfiltered_data = data.dup
805
+ @name_entry.text = ''
806
+ @email_entry.text = ''
807
+ @phone_entry.text = ''
808
+ @city_entry.text = ''
809
+ @state_entry.text = ''
810
+ end
811
+ end
812
+ }
813
+
814
+ search_entry { |se|
815
+ stretchy false
816
+
817
+ on_changed do
818
+ filter_value = se.text
819
+ @unfiltered_data ||= data.dup
820
+ # Unfilter first to remove any previous filters
821
+ data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
822
+ # Now, apply filter if entered
823
+ unless filter_value.empty?
824
+ data.filter! do |row_data| # affects table indirectly through implicit data-binding
825
+ row_data.any? do |cell|
826
+ cell.to_s.downcase.include?(filter_value.downcase)
827
+ end
828
+ end
829
+ end
830
+ end
831
+ }
832
+
833
+ table {
834
+ text_column('Name')
835
+ text_column('Email')
836
+ text_column('Phone')
837
+ text_column('City')
838
+ text_column('State')
839
+
840
+ editable true
841
+ cell_rows data # implicit data-binding to raw data Array of Arrays
842
+
843
+ on_changed do |row, type, row_data|
844
+ puts "Row #{row} #{type}: #{row_data}"
845
+ end
846
+ }
847
+ }
848
+ }.show
849
+ ```
850
+
851
+ ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png)
852
+
853
+ Learn more by checking out [examples](#examples).
854
+
855
+ ### Area API
856
+
857
+ The `area` control is a canvas-like control for drawing paths that can be used in one of two ways:
858
+ - Declaratively via stable paths: useful for stable paths that will not change often later on. Simply nest `path` and figures like `rectangle` and all drawing logic is generated automatically. Path proxy objects are preserved across redraws assuming there would be relatively few stable paths (mostly for decorative reasons).
859
+ - Semi-declaratively via on_draw listener dynamic paths: useful for more dynamic paths that will definitely change very often. Open an `on_draw` listener block that receives an [`area_draw_params`](#area-draw-params) argument and nest `path` and figures like `rectangle` and all drawing logic is generated automatically. Path proxy objects are destroyed (thrown-away) at the end of drawing, thus having less memory overhead for drawing thousands of dynamic paths.
860
+
861
+ Note that when nesting an `area` directly underneath `window` (without a layout control like `vertical_box`), it is automatically reparented with `vertical_box` in between the `window` and `area` since it would not show up on Linux otherwise.
862
+
863
+ Here is an example of a declarative `area` with a stable path (you may copy/paste in [`girb`](#girb-glimmer-irb)):
864
+
865
+ ```ruby
866
+ require 'glimmer-dsl-libui'
867
+
868
+ include Glimmer
869
+
870
+ window('Basic Area', 400, 400) {
871
+ margined true
872
+
873
+ vertical_box {
874
+ area {
875
+ path { # a stable path is added declaratively
876
+ rectangle(0, 0, 400, 400)
877
+
878
+ fill r: 102, g: 102, b: 204, a: 1.0
879
+ }
880
+ }
881
+ }
882
+ }.show
883
+ ```
884
+
885
+ Mac | Windows | Linux
766
886
  ----|---------|------
767
887
  ![glimmer-dsl-libui-mac-basic-area.png](images/glimmer-dsl-libui-mac-basic-area.png) | ![glimmer-dsl-libui-windows-basic-area.png](images/glimmer-dsl-libui-windows-basic-area.png) | ![glimmer-dsl-libui-linux-basic-area.png](images/glimmer-dsl-libui-linux-basic-area.png)
768
888
 
@@ -798,9 +918,9 @@ Check [examples/dynamic_area.rb](#dynamic-area) for a more detailed semi-declara
798
918
  - `scroll_to(x as Numeric, y as Numeric, width as Numeric = main_window.width, height as Numeric = main_window.height)`: scrolls to `x`/`y` location with `width` and `height` viewport size.
799
919
  - `set_size(width as Numeric, height as Numeric)`: set size of scrolling area, which must must exceed that of visible viewport in order for scrolling to be enabled.
800
920
 
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)
921
+ Mac | Windows | Linux
922
+ ----|---------|------
923
+ ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png) | ![glimmer-dsl-libui-windows-dynamic-area.png](images/glimmer-dsl-libui-windows-basic-scrolling-area.png) ![glimmer-dsl-libui-windows-dynamic-area-updated.png](images/glimmer-dsl-libui-windows-basic-scrolling-area-scrolled.png) | ![glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-basic-scrolling-area.png) ![glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-basic-scrolling-area-scrolled.png)
804
924
 
805
925
  Check [examples/basic_scrolling_area.rb](#basic-scrolling-area) for a more detailed example.
806
926
 
@@ -1216,6 +1336,7 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t
1216
1336
  - When destroying a control nested under a `horizontal_box` or `vertical_box`, it is automatically deleted from the box's children
1217
1337
  - When destroying a control nested under a `form`, it is automatically deleted from the form's children
1218
1338
  - When destroying a control nested under a `window` or `group`, it is automatically unset as their child to allow successful destruction
1339
+ - When destroying a control that has a data-binding to a model attribute, the data-binding observer registration is automatically deregistered
1219
1340
  - For `date_time_picker`, `date_picker`, and `time_picker`, make sure `time` hash values for `mon`, `wday`, and `yday` are 1-based instead of [libui](https://github.com/andlabs/libui) original 0-based values, and return `dst` as Boolean instead of `isdst` as `1`/`0`
1220
1341
  - Smart defaults for `grid` child properties are `left` (`0`), `top` (`0`), `xspan` (`1`), `yspan` (`1`), `hexpand` (`false`), `halign` (`:fill`), `vexpand` (`false`), and `valign` (`:fill`)
1221
1342
  - The `table` control automatically constructs required `TableModelHandler`, `TableModel`, and `TableParams`, calculating all their arguments from `cell_rows` and `editable` properties (e.g. `NumRows`) as well as nested columns (e.g. `text_column`)
@@ -1382,6 +1503,8 @@ Data-binding supports utilizing the [MVP (Model View Presenter)](https://en.wiki
1382
1503
  ![MVP](https://www.researchgate.net/profile/Gilles-Perrouin/publication/320249584/figure/fig8/AS:668260987068418@1536337243385/Model-view-presenter-architecture.png)
1383
1504
 
1384
1505
  [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):
1506
+ - `checkbox`: `checked`
1507
+ - `check_menu_item`: `checked`
1385
1508
  - `color_button`: `color`
1386
1509
  - `combobox`: `selected`, `selected_item`
1387
1510
  - `date_picker`: `time`
@@ -1391,9 +1514,12 @@ Data-binding supports utilizing the [MVP (Model View Presenter)](https://en.wiki
1391
1514
  - `font_button`: `font`
1392
1515
  - `multiline_entry`: `text`
1393
1516
  - `non_wrapping_multiline_entry`: `text`
1517
+ - `radio_buttons`: `selected`
1518
+ - `radio_menu_item`: `checked`
1394
1519
  - `search_entry`: `text`
1395
1520
  - `slider`: `value`
1396
1521
  - `spinbox`: `value`
1522
+ - `table`: `cell_rows` (explicit data-binding by using `<=>` and [implicit data-binding](#table-api) by assigning value directly)
1397
1523
  - `time_picker`: `time`
1398
1524
 
1399
1525
  Example of bidirectional data-binding:
@@ -1485,272 +1611,12 @@ Learn more from data-binding usage in [Login](#login) (4 data-binding versions),
1485
1611
  - `table` `progress_bar` column on Windows cannot be updated with a positive value if it started initially with `-1` (it ignores update to avoid crashing due to an issue in [libui](https://github.com/andlabs/libui) on Windows.
1486
1612
  - It seems that [libui](https://github.com/andlabs/libui) does not support nesting multiple `area` controls under a `grid` as only the first one shows up in that scenario. To workaround that limitation, use a `vertical_box` with nested `horizontal_box`s instead to include multiple `area`s in a GUI.
1487
1613
  - As per the code of [examples/basic_transform.rb](#basic-transform), Windows requires different ordering of transforms than Mac and Linux.
1614
+ - `scrolling_area#scroll_to` does not seem to work on Windows and Linux, but works fine on Mac
1488
1615
 
1489
1616
  ### Original API
1490
1617
 
1491
1618
  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):
1492
- - `alloc_control`
1493
- - `area_begin_user_window_move`
1494
- - `area_begin_user_window_resize`
1495
- - `area_queue_redraw_all`
1496
- - `area_scroll_to`
1497
- - `area_set_size`
1498
- - `attribute_color`
1499
- - `attribute_family`
1500
- - `attribute_features`
1501
- - `attribute_get_type`
1502
- - `attribute_italic`
1503
- - `attribute_size`
1504
- - `attribute_stretch`
1505
- - `attribute_underline`
1506
- - `attribute_underline_color`
1507
- - `attribute_weight`
1508
- - `attributed_string_append_unattributed`
1509
- - `attributed_string_byte_index_to_grapheme`
1510
- - `attributed_string_delete`
1511
- - `attributed_string_for_each_attribute`
1512
- - `attributed_string_grapheme_to_byte_index`
1513
- - `attributed_string_insert_at_unattributed`
1514
- - `attributed_string_len`
1515
- - `attributed_string_num_graphemes`
1516
- - `attributed_string_set_attribute`
1517
- - `attributed_string_string`
1518
- - `box_append`
1519
- - `box_delete`
1520
- - `box_padded`
1521
- - `box_set_padded`
1522
- - `button_on_clicked`
1523
- - `button_set_text`
1524
- - `button_text`
1525
- - `checkbox_checked`
1526
- - `checkbox_on_toggled`
1527
- - `checkbox_set_checked`
1528
- - `checkbox_set_text`
1529
- - `checkbox_text`
1530
- - `color_button_color`
1531
- - `color_button_on_changed`
1532
- - `color_button_set_color`
1533
- - `combobox_append`
1534
- - `combobox_on_selected`
1535
- - `combobox_selected`
1536
- - `combobox_set_selected`
1537
- - `control_destroy`
1538
- - `control_disable`
1539
- - `control_enable`
1540
- - `control_enabled`
1541
- - `control_enabled_to_user`
1542
- - `control_handle`
1543
- - `control_hide`
1544
- - `control_parent`
1545
- - `control_set_parent`
1546
- - `control_show`
1547
- - `control_toplevel`
1548
- - `control_verify_set_parent`
1549
- - `control_visible`
1550
- - `date_time_picker_on_changed`
1551
- - `date_time_picker_set_time`
1552
- - `date_time_picker_time`
1553
- - `draw_clip`
1554
- - `draw_fill`
1555
- - `draw_free_path`
1556
- - `draw_free_text_layout`
1557
- - `draw_matrix_invert`
1558
- - `draw_matrix_invertible`
1559
- - `draw_matrix_multiply`
1560
- - `draw_matrix_rotate`
1561
- - `draw_matrix_scale`
1562
- - `draw_matrix_set_identity`
1563
- - `draw_matrix_skew`
1564
- - `draw_matrix_transform_point`
1565
- - `draw_matrix_transform_size`
1566
- - `draw_matrix_translate`
1567
- - `draw_new_path`
1568
- - `draw_new_text_layout`
1569
- - `draw_path_add_rectangle`
1570
- - `draw_path_arc_to`
1571
- - `draw_path_bezier_to`
1572
- - `draw_path_close_figure`
1573
- - `draw_path_end`
1574
- - `draw_path_line_to`
1575
- - `draw_path_new_figure`
1576
- - `draw_path_new_figure_with_arc`
1577
- - `draw_restore`
1578
- - `draw_save`
1579
- - `draw_stroke`
1580
- - `draw_text`
1581
- - `draw_text_layout_extents`
1582
- - `draw_transform`
1583
- - `editable_combobox_append`
1584
- - `editable_combobox_on_changed`
1585
- - `editable_combobox_set_text`
1586
- - `editable_combobox_text`
1587
- - `entry_on_changed`
1588
- - `entry_read_only`
1589
- - `entry_set_read_only`
1590
- - `entry_set_text`
1591
- - `entry_text`
1592
- - `ffi_lib`
1593
- - `ffi_lib=`
1594
- - `font_button_font`
1595
- - `font_button_on_changed`
1596
- - `form_append`
1597
- - `form_delete`
1598
- - `form_padded`
1599
- - `form_set_padded`
1600
- - `free_attribute`
1601
- - `free_attributed_string`
1602
- - `free_control`
1603
- - `free_font_button_font`
1604
- - `free_image`
1605
- - `free_init_error`
1606
- - `free_open_type_features`
1607
- - `free_table_model`
1608
- - `free_table_value`
1609
- - `free_text`
1610
- - `grid_append`
1611
- - `grid_insert_at`
1612
- - `grid_padded`
1613
- - `grid_set_padded`
1614
- - `group_margined`
1615
- - `group_set_child`
1616
- - `group_set_margined`
1617
- - `group_set_title`
1618
- - `group_title`
1619
- - `image_append`
1620
- - `init`
1621
- - `label_set_text`
1622
- - `label_text`
1623
- - `main`
1624
- - `main_step`
1625
- - `main_steps`
1626
- - `menu_append_about_item`
1627
- - `menu_append_check_item`
1628
- - `menu_append_item`
1629
- - `menu_append_preferences_item`
1630
- - `menu_append_quit_item`
1631
- - `menu_append_separator`
1632
- - `menu_item_checked`
1633
- - `menu_item_disable`
1634
- - `menu_item_enable`
1635
- - `menu_item_on_clicked`
1636
- - `menu_item_set_checked`
1637
- - `msg_box`
1638
- - `msg_box_error`
1639
- - `multiline_entry_append`
1640
- - `multiline_entry_on_changed`
1641
- - `multiline_entry_read_only`
1642
- - `multiline_entry_set_read_only`
1643
- - `multiline_entry_set_text`
1644
- - `multiline_entry_text`
1645
- - `new_area`
1646
- - `new_attributed_string`
1647
- - `new_background_attribute`
1648
- - `new_button`
1649
- - `new_checkbox`
1650
- - `new_color_attribute`
1651
- - `new_color_button`
1652
- - `new_combobox`
1653
- - `new_date_picker`
1654
- - `new_date_time_picker`
1655
- - `new_editable_combobox`
1656
- - `new_entry`
1657
- - `new_family_attribute`
1658
- - `new_features_attribute`
1659
- - `new_font_button`
1660
- - `new_form`
1661
- - `new_grid`
1662
- - `new_group`
1663
- - `new_horizontal_box`
1664
- - `new_horizontal_separator`
1665
- - `new_image`
1666
- - `new_italic_attribute`
1667
- - `new_label`
1668
- - `new_menu`
1669
- - `new_multiline_entry`
1670
- - `new_non_wrapping_multiline_entry`
1671
- - `new_open_type_features`
1672
- - `new_password_entry`
1673
- - `new_progress_bar`
1674
- - `new_radio_buttons`
1675
- - `new_scrolling_area`
1676
- - `new_search_entry`
1677
- - `new_size_attribute`
1678
- - `new_slider`
1679
- - `new_spinbox`
1680
- - `new_stretch_attribute`
1681
- - `new_tab`
1682
- - `new_table`
1683
- - `new_table_model`
1684
- - `new_table_value_color`
1685
- - `new_table_value_image`
1686
- - `new_table_value_int`
1687
- - `new_table_value_string`
1688
- - `new_time_picker`
1689
- - `new_underline_attribute`
1690
- - `new_underline_color_attribute`
1691
- - `new_vertical_box`
1692
- - `new_vertical_separator`
1693
- - `new_weight_attribute`
1694
- - `new_window`
1695
- - `on_should_quit`
1696
- - `open_file`
1697
- - `open_type_features_add`
1698
- - `open_type_features_clone`
1699
- - `open_type_features_for_each`
1700
- - `open_type_features_get`
1701
- - `open_type_features_remove`
1702
- - `progress_bar_set_value`
1703
- - `progress_bar_value`
1704
- - `queue_main`
1705
- - `quit`
1706
- - `radio_buttons_append`
1707
- - `radio_buttons_on_selected`
1708
- - `radio_buttons_selected`
1709
- - `radio_buttons_set_selected`
1710
- - `save_file`
1711
- - `slider_on_changed`
1712
- - `slider_set_value`
1713
- - `slider_value`
1714
- - `spinbox_on_changed`
1715
- - `spinbox_set_value`
1716
- - `spinbox_value`
1717
- - `tab_append`
1718
- - `tab_delete`
1719
- - `tab_insert_at`
1720
- - `tab_margined`
1721
- - `tab_num_pages`
1722
- - `tab_set_margined`
1723
- - `table_append_button_column`
1724
- - `table_append_checkbox_column`
1725
- - `table_append_checkbox_text_column`
1726
- - `table_append_image_column`
1727
- - `table_append_image_text_column`
1728
- - `table_append_progress_bar_column`
1729
- - `table_append_text_column`
1730
- - `table_model_row_changed`
1731
- - `table_model_row_deleted`
1732
- - `table_model_row_inserted`
1733
- - `table_value_color`
1734
- - `table_value_get_type`
1735
- - `table_value_image`
1736
- - `table_value_int`
1737
- - `table_value_string`
1738
- - `timer`
1739
- - `uninit`
1740
- - `user_bug_cannot_set_parent_on_toplevel`
1741
- - `window_borderless`
1742
- - `window_content_size`
1743
- - `window_fullscreen`
1744
- - `window_margined`
1745
- - `window_on_closing`
1746
- - `window_on_content_size_changed`
1747
- - `window_set_borderless`
1748
- - `window_set_child`
1749
- - `window_set_content_size`
1750
- - `window_set_fullscreen`
1751
- - `window_set_margined`
1752
- - `window_set_title`
1753
- - `window_title`
1619
+ `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`
1754
1620
 
1755
1621
  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):
1756
1622
  - Check out [LibUI ffi.rb](https://github.com/kojix2/LibUI/blob/main/lib/libui/ffi.rb)
@@ -3264,7 +3130,70 @@ Mac | Windows | Linux
3264
3130
  ----|---------|------
3265
3131
  ![glimmer-dsl-libui-mac-basic-table-button.png](images/glimmer-dsl-libui-mac-basic-table-button.png) ![glimmer-dsl-libui-mac-basic-table-button-deleted.png](images/glimmer-dsl-libui-mac-basic-table-button-deleted.png) | ![glimmer-dsl-libui-windows-basic-table-button.png](images/glimmer-dsl-libui-windows-basic-table-button.png) ![glimmer-dsl-libui-windows-basic-table-button-deleted.png](images/glimmer-dsl-libui-windows-basic-table-button-deleted.png) | ![glimmer-dsl-libui-linux-basic-table-button.png](images/glimmer-dsl-libui-linux-basic-table-button.png) ![glimmer-dsl-libui-linux-basic-table-button-deleted.png](images/glimmer-dsl-libui-linux-basic-table-button-deleted.png)
3266
3132
 
3267
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3133
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
3134
+
3135
+ ```ruby
3136
+ require 'glimmer-dsl-libui'
3137
+
3138
+ class BasicTableButton
3139
+ BasicAnimal = Struct.new(:name, :sound)
3140
+
3141
+ class Animal < BasicAnimal
3142
+ def action
3143
+ 'delete'
3144
+ end
3145
+ end
3146
+
3147
+ include Glimmer
3148
+
3149
+ attr_accessor :animals
3150
+
3151
+ def initialize
3152
+ @animals = [
3153
+ Animal.new('cat', 'meow'),
3154
+ Animal.new('dog', 'woof'),
3155
+ Animal.new('chicken', 'cock-a-doodle-doo'),
3156
+ Animal.new('horse', 'neigh'),
3157
+ Animal.new('cow', 'moo'),
3158
+ ]
3159
+ end
3160
+
3161
+ def launch
3162
+ window('Animal sounds', 400, 200) {
3163
+ horizontal_box {
3164
+ table {
3165
+ text_column('Animal')
3166
+ text_column('Description')
3167
+ button_column('Action') {
3168
+ on_clicked do |row|
3169
+ # Option 1: direct data deletion is the simpler solution
3170
+ # @animals.delete_at(row) # automatically deletes actual table row due to explicit data-binding
3171
+
3172
+ # Option 2: cloning only to demonstrate table row deletion upon explicit setting of animals attribute (cloning is not recommended beyond demonstrating this point)
3173
+ new_animals = @animals.clone
3174
+ new_animals.delete_at(row)
3175
+ self.animals = new_animals # automatically loses deleted table row due to explicit data-binding
3176
+ end
3177
+ }
3178
+
3179
+
3180
+ cell_rows <= [self, :animals, column_attributes: {'Animal' => :name, 'Description' => :sound}]
3181
+
3182
+ # explicit unidirectional data-binding of table cell_rows to self.animals
3183
+ on_changed do |row, type, row_data|
3184
+ puts "Row #{row} #{type}: #{row_data}"
3185
+ $stdout.flush
3186
+ end
3187
+ }
3188
+ }
3189
+ }.show
3190
+ end
3191
+ end
3192
+
3193
+ BasicTableButton.new.launch
3194
+ ```
3195
+
3196
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (with implicit [data-binding](#data-binding)):
3268
3197
 
3269
3198
  ```ruby
3270
3199
  require 'glimmer-dsl-libui'
@@ -3499,7 +3428,7 @@ window('Animals', 500, 200) {
3499
3428
  text_color_column('Sound')
3500
3429
  checkbox_text_color_column('Description')
3501
3430
  image_text_color_column('GUI')
3502
- background_color_column('Mammal')
3431
+ background_color_column # must be the last column
3503
3432
 
3504
3433
  cell_rows data
3505
3434
  }
@@ -3541,7 +3470,7 @@ window('Animals', 500, 200) {
3541
3470
  text_color_column('Sound')
3542
3471
  checkbox_text_color_column('Description')
3543
3472
  image_text_color_column('GUI')
3544
- background_color_column('Mammal')
3473
+ background_color_column
3545
3474
 
3546
3475
  cell_rows data
3547
3476
  }
@@ -3683,9 +3612,9 @@ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/
3683
3612
  ruby -r glimmer-dsl-libui -e "require 'examples/basic_scrolling_area'"
3684
3613
  ```
3685
3614
 
3686
- Mac | Linux
3687
- ----|------
3688
- ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png) | ![glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-basic-scrolling-area.png) ![glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-basic-scrolling-area-scrolled.png)
3615
+ Mac | Windows | Linux
3616
+ ----|---------|------
3617
+ ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png) | ![glimmer-dsl-libui-windows-dynamic-area.png](images/glimmer-dsl-libui-windows-basic-scrolling-area.png) ![glimmer-dsl-libui-windows-dynamic-area-updated.png](images/glimmer-dsl-libui-windows-basic-scrolling-area-scrolled.png) | ![glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-basic-scrolling-area.png) ![glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-basic-scrolling-area-scrolled.png)
3689
3618
 
3690
3619
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3691
3620
 
@@ -3772,6 +3701,8 @@ BasicScrollingArea.new.launch
3772
3701
 
3773
3702
  #### Basic Image
3774
3703
 
3704
+ Please note the caveats of [Area Image](#area-image) **(Alpha Feature)** with regards to this example.
3705
+
3775
3706
  [examples/basic_image.rb](examples/basic_image.rb)
3776
3707
 
3777
3708
  Run with this command from the root of the project if you cloned the project:
@@ -5022,9 +4953,9 @@ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/
5022
4953
  ruby -r glimmer-dsl-libui -e "require 'examples/button_counter'"
5023
4954
  ```
5024
4955
 
5025
- Mac | Linux
5026
- ----|------
5027
- ![glimmer-dsl-libui-mac-button-counter.png](images/glimmer-dsl-libui-mac-button-counter.png) | ![glimmer-dsl-libui-linux-button-counter.png](images/glimmer-dsl-libui-linux-button-counter.png)
4956
+ Mac | Windows | Linux
4957
+ ----|---------|------
4958
+ ![glimmer-dsl-libui-mac-button-counter.png](images/glimmer-dsl-libui-mac-button-counter.png) | ![glimmer-dsl-libui-windows-button-counter.png](images/glimmer-dsl-libui-windows-button-counter.png) | ![glimmer-dsl-libui-linux-button-counter.png](images/glimmer-dsl-libui-linux-button-counter.png)
5028
4959
 
5029
4960
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
5030
4961
 
@@ -5043,7 +4974,8 @@ class ButtonCounter
5043
4974
  def launch
5044
4975
  window('Hello, Button!') {
5045
4976
  button {
5046
- text <= [self, :count, on_read: ->(count) {"Count: #{count}"}] # data-bind button text to self count, converting to string on read.
4977
+ # data-bind button text to self count, converting to string on read.
4978
+ text <= [self, :count, on_read: ->(count) {"Count: #{count}"}]
5047
4979
 
5048
4980
  on_clicked do
5049
4981
  self.count += 1
@@ -5687,6 +5619,71 @@ MAIN_WINDOW = window('Control Gallery', 600, 500) {
5687
5619
  MAIN_WINDOW.show
5688
5620
  ```
5689
5621
 
5622
+ #### CPU Percentage
5623
+
5624
+ This example shows CPU usage percentage second by second.
5625
+
5626
+ 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.
5627
+
5628
+ [examples/cpu_percentage.rb](examples/cpu_percentage.rb)
5629
+
5630
+ Run with this command from the root of the project if you cloned the project:
5631
+
5632
+ ```
5633
+ ruby -r './lib/glimmer-dsl-libui' examples/cpu_percentage.rb
5634
+ ```
5635
+
5636
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
5637
+
5638
+ ```
5639
+ ruby -r glimmer-dsl-libui -e "require 'examples/cpu_percentage'"
5640
+ ```
5641
+
5642
+ Mac | Windows | Linux
5643
+ ----|---------|------
5644
+ ![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)
5645
+
5646
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
5647
+
5648
+ ```ruby
5649
+ require 'glimmer-dsl-libui'
5650
+ require 'bigdecimal'
5651
+
5652
+ include Glimmer
5653
+
5654
+ data = [
5655
+ ['CPU', '0%', 0],
5656
+ ]
5657
+
5658
+ Glimmer::LibUI.timer(1) do
5659
+ cpu_percentage_value = nil
5660
+ if OS.windows?
5661
+ cpu_percentage_raw_value = `wmic cpu get loadpercentage`
5662
+ cpu_percentage_value = cpu_percentage_raw_value.split("\n")[2].to_i
5663
+ elsif OS.mac?
5664
+ cpu_percentage_value = `ps -A -o %cpu | awk '{s+=$1} END {print s}'`.to_i
5665
+ elsif OS.linux?
5666
+ stats = `top -n 1`
5667
+ idle_percentage = stats.split("\n")[2].match(/ni,.* (.*) .*id/)[1]
5668
+ cpu_percentage_value = (BigDecimal(100) - BigDecimal(idle_percentage)).to_i
5669
+ end
5670
+ data[0][1] = "#{cpu_percentage_value}%"
5671
+ data[0][2] = cpu_percentage_value
5672
+ end
5673
+
5674
+ window('CPU Percentage', 400, 200) {
5675
+ vertical_box {
5676
+ table {
5677
+ text_column('Name')
5678
+ text_column('Value')
5679
+ progress_bar_column('Percentage')
5680
+
5681
+ cell_rows data # implicit data-binding
5682
+ }
5683
+ }
5684
+ }.show
5685
+ ```
5686
+
5690
5687
  #### Custom Draw Text
5691
5688
 
5692
5689
  [examples/custom_draw_text.rb](examples/custom_draw_text.rb)
@@ -6397,8 +6394,8 @@ window('Editable animal sounds', 300, 200) {
6397
6394
  text_column('Animal')
6398
6395
  text_column('Description')
6399
6396
 
6400
- cell_rows data
6401
6397
  editable true
6398
+ cell_rows data
6402
6399
 
6403
6400
  on_changed do |row, type, row_data| # fires on all changes (even ones happening through data array)
6404
6401
  puts "Row #{row} #{type}: #{row_data}"
@@ -6413,30 +6410,382 @@ window('Editable animal sounds', 300, 200) {
6413
6410
  on_closing do
6414
6411
  puts 'Bye Bye'
6415
6412
  end
6416
- }.show
6417
- ```
6418
-
6419
- #### Form Table
6420
-
6421
- [examples/form_table.rb](examples/form_table.rb)
6422
-
6423
- Run with this command from the root of the project if you cloned the project:
6424
-
6425
- ```
6426
- ruby -r './lib/glimmer-dsl-libui' examples/form_table.rb
6427
- ```
6428
-
6429
- Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
6413
+ }.show
6414
+ ```
6415
+
6416
+ #### Form Table
6417
+
6418
+ [examples/form_table.rb](examples/form_table.rb)
6419
+
6420
+ Run with this command from the root of the project if you cloned the project:
6421
+
6422
+ ```
6423
+ ruby -r './lib/glimmer-dsl-libui' examples/form_table.rb
6424
+ ```
6425
+
6426
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
6427
+
6428
+ ```
6429
+ ruby -r glimmer-dsl-libui -e "require 'examples/form_table'"
6430
+ ```
6431
+
6432
+ Mac | Windows | Linux
6433
+ ----|---------|------
6434
+ ![glimmer-dsl-libui-mac-form-table.png](images/glimmer-dsl-libui-mac-form-table.png) ![glimmer-dsl-libui-mac-form-table-contact-entered.png](images/glimmer-dsl-libui-mac-form-table-contact-entered.png) ![glimmer-dsl-libui-mac-form-table-filtered.png](images/glimmer-dsl-libui-mac-form-table-filtered.png) | ![glimmer-dsl-libui-windows-form-table.png](images/glimmer-dsl-libui-windows-form-table.png) ![glimmer-dsl-libui-windows-form-table-contact-entered.png](images/glimmer-dsl-libui-windows-form-table-contact-entered.png) ![glimmer-dsl-libui-windows-form-table-filtered.png](images/glimmer-dsl-libui-windows-form-table-filtered.png) | ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png) ![glimmer-dsl-libui-linux-form-table-contact-entered.png](images/glimmer-dsl-libui-linux-form-table-contact-entered.png) ![glimmer-dsl-libui-linux-form-table-filtered.png](images/glimmer-dsl-libui-linux-form-table-filtered.png)
6435
+
6436
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
6437
+
6438
+ ```ruby
6439
+ require 'glimmer-dsl-libui'
6440
+
6441
+ class FormTable
6442
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
6443
+
6444
+ include Glimmer
6445
+
6446
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
6447
+
6448
+ def initialize
6449
+ @contacts = [
6450
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
6451
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
6452
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
6453
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
6454
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
6455
+ ]
6456
+ end
6457
+
6458
+ def launch
6459
+ window('Contacts', 600, 600) { |w|
6460
+ margined true
6461
+
6462
+ vertical_box {
6463
+ form {
6464
+ stretchy false
6465
+
6466
+ entry {
6467
+ label 'Name'
6468
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6469
+ }
6470
+
6471
+ entry {
6472
+ label 'Email'
6473
+ text <=> [self, :email]
6474
+ }
6475
+
6476
+ entry {
6477
+ label 'Phone'
6478
+ text <=> [self, :phone]
6479
+ }
6480
+
6481
+ entry {
6482
+ label 'City'
6483
+ text <=> [self, :city]
6484
+ }
6485
+
6486
+ entry {
6487
+ label 'State'
6488
+ text <=> [self, :state]
6489
+ }
6490
+ }
6491
+
6492
+ button('Save Contact') {
6493
+ stretchy false
6494
+
6495
+ on_clicked do
6496
+ new_row = [name, email, phone, city, state]
6497
+ if new_row.include?('')
6498
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6499
+ else
6500
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to explicit data-binding
6501
+ @unfiltered_contacts = @contacts.dup
6502
+ self.name = '' # automatically clears name entry through explicit data-binding
6503
+ self.email = ''
6504
+ self.phone = ''
6505
+ self.city = ''
6506
+ self.state = ''
6507
+ end
6508
+ end
6509
+ }
6510
+
6511
+ search_entry {
6512
+ stretchy false
6513
+ # bidirectional data-binding of text to self.filter_value with after_write option
6514
+ text <=> [self, :filter_value,
6515
+ after_write: ->(filter_value) { # execute after write to self.filter_value
6516
+ @unfiltered_contacts ||= @contacts.dup
6517
+ # Unfilter first to remove any previous filters
6518
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
6519
+ # Now, apply filter if entered
6520
+ unless filter_value.empty?
6521
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
6522
+ contact.members.any? do |attribute|
6523
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
6524
+ end
6525
+ end
6526
+ end
6527
+ }
6528
+ ]
6529
+ }
6530
+
6531
+ table {
6532
+ text_column('Name')
6533
+ text_column('Email')
6534
+ text_column('Phone')
6535
+ text_column('City')
6536
+ text_column('State')
6537
+
6538
+ editable true
6539
+ cell_rows <=> [self, :contacts] # explicit data-binding to Model Array
6540
+
6541
+ on_changed do |row, type, row_data|
6542
+ puts "Row #{row} #{type}: #{row_data}"
6543
+ end
6544
+ }
6545
+ }
6546
+ }.show
6547
+ end
6548
+ end
6549
+
6550
+ FormTable.new.launch
6551
+ ```
6552
+
6553
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
6554
+
6555
+ ```ruby
6556
+ require 'glimmer-dsl-libui'
6557
+
6558
+ class FormTable
6559
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
6560
+
6561
+ include Glimmer
6562
+
6563
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
6564
+
6565
+ def initialize
6566
+ @contacts = [
6567
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
6568
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
6569
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
6570
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
6571
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
6572
+ ]
6573
+ end
6574
+
6575
+ def launch
6576
+ window('Contacts', 600, 600) { |w|
6577
+ margined true
6578
+
6579
+ vertical_box {
6580
+ form {
6581
+ stretchy false
6582
+
6583
+ entry {
6584
+ label 'Name'
6585
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6586
+ }
6587
+
6588
+ entry {
6589
+ label 'Email'
6590
+ text <=> [self, :email]
6591
+ }
6592
+
6593
+ entry {
6594
+ label 'Phone'
6595
+ text <=> [self, :phone]
6596
+ }
6597
+
6598
+ entry {
6599
+ label 'City'
6600
+ text <=> [self, :city]
6601
+ }
6602
+
6603
+ entry {
6604
+ label 'State'
6605
+ text <=> [self, :state]
6606
+ }
6607
+ }
6608
+
6609
+ button('Save Contact') {
6610
+ stretchy false
6611
+
6612
+ on_clicked do
6613
+ new_row = [name, email, phone, city, state]
6614
+ if new_row.include?('')
6615
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6616
+ else
6617
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to implicit data-binding
6618
+ @unfiltered_contacts = @contacts.dup
6619
+ self.name = '' # automatically clears name entry through explicit data-binding
6620
+ self.email = ''
6621
+ self.phone = ''
6622
+ self.city = ''
6623
+ self.state = ''
6624
+ end
6625
+ end
6626
+ }
6627
+
6628
+ search_entry {
6629
+ stretchy false
6630
+ # bidirectional data-binding of text to self.filter_value with after_write option
6631
+ text <=> [self, :filter_value,
6632
+ after_write: ->(filter_value) { # execute after write to self.filter_value
6633
+ @unfiltered_contacts ||= @contacts.dup
6634
+ # Unfilter first to remove any previous filters
6635
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
6636
+ # Now, apply filter if entered
6637
+ unless filter_value.empty?
6638
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
6639
+ contact.members.any? do |attribute|
6640
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
6641
+ end
6642
+ end
6643
+ end
6644
+ }
6645
+ ]
6646
+ }
6647
+
6648
+ table {
6649
+ text_column('Name')
6650
+ text_column('Email')
6651
+ text_column('Phone')
6652
+ text_column('City')
6653
+ text_column('State/Province')
6654
+
6655
+ editable true
6656
+ cell_rows <=> [self, :contacts, column_attributes: {'State/Province' => :state}] # explicit data-binding to Model Array with column_attributes mapping for a specific column
6657
+
6658
+ on_changed do |row, type, row_data|
6659
+ puts "Row #{row} #{type}: #{row_data}"
6660
+ end
6661
+ }
6662
+ }
6663
+ }.show
6664
+ end
6665
+ end
6666
+
6667
+ FormTable.new.launch
6668
+ ```
6669
+
6670
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with explicit [data-binding](#data-binding)):
6671
+
6672
+ ```ruby
6673
+
6674
+ require 'glimmer-dsl-libui'
6675
+
6676
+ class FormTable
6677
+ Contact = Struct.new(:full_name, :email_address, :phone_number, :city_or_town, :state_or_province)
6678
+
6679
+ include Glimmer
6680
+
6681
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
6682
+
6683
+ def initialize
6684
+ @contacts = [
6685
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
6686
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
6687
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
6688
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
6689
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
6690
+ ]
6691
+ end
6692
+
6693
+ def launch
6694
+ window('Contacts', 600, 600) { |w|
6695
+ margined true
6696
+
6697
+ vertical_box {
6698
+ form {
6699
+ stretchy false
6700
+
6701
+ entry {
6702
+ label 'Name'
6703
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6704
+ }
6705
+
6706
+ entry {
6707
+ label 'Email'
6708
+ text <=> [self, :email]
6709
+ }
6710
+
6711
+ entry {
6712
+ label 'Phone'
6713
+ text <=> [self, :phone]
6714
+ }
6715
+
6716
+ entry {
6717
+ label 'City'
6718
+ text <=> [self, :city]
6719
+ }
6720
+
6721
+ entry {
6722
+ label 'State'
6723
+ text <=> [self, :state]
6724
+ }
6725
+ }
6726
+
6727
+ button('Save Contact') {
6728
+ stretchy false
6729
+
6730
+ on_clicked do
6731
+ new_row = [name, email, phone, city, state]
6732
+ if new_row.include?('')
6733
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6734
+ else
6735
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to implicit data-binding
6736
+ @unfiltered_contacts = @contacts.dup
6737
+ self.name = '' # automatically clears name entry through explicit data-binding
6738
+ self.email = ''
6739
+ self.phone = ''
6740
+ self.city = ''
6741
+ self.state = ''
6742
+ end
6743
+ end
6744
+ }
6745
+
6746
+ search_entry {
6747
+ stretchy false
6748
+ # bidirectional data-binding of text to self.filter_value with after_write option
6749
+ text <=> [self, :filter_value,
6750
+ after_write: ->(filter_value) { # execute after write to self.filter_value
6751
+ @unfiltered_contacts ||= @contacts.dup
6752
+ # Unfilter first to remove any previous filters
6753
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
6754
+ # Now, apply filter if entered
6755
+ unless filter_value.empty?
6756
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
6757
+ contact.members.any? do |attribute|
6758
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
6759
+ end
6760
+ end
6761
+ end
6762
+ }
6763
+ ]
6764
+ }
6765
+
6766
+ table {
6767
+ text_column('Name')
6768
+ text_column('Email')
6769
+ text_column('Phone')
6770
+ text_column('City')
6771
+ text_column('State')
6772
+
6773
+ editable true
6774
+ cell_rows <=> [self, :contacts, column_attributes: [:full_name, :email_address, :phone_number, :city_or_town, :state_or_province]] # explicit data-binding to Model Array with column_attributes mapping for all columns
6775
+
6776
+ on_changed do |row, type, row_data|
6777
+ puts "Row #{row} #{type}: #{row_data}"
6778
+ end
6779
+ }
6780
+ }
6781
+ }.show
6782
+ end
6783
+ end
6430
6784
 
6785
+ FormTable.new.launch
6431
6786
  ```
6432
- ruby -r glimmer-dsl-libui -e "require 'examples/form_table'"
6433
- ```
6434
-
6435
- Mac | Windows | Linux
6436
- ----|---------|------
6437
- ![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)
6438
6787
 
6439
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
6788
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (with explicit [data-binding](#data-binding) to raw data):
6440
6789
 
6441
6790
  ```ruby
6442
6791
  require 'glimmer-dsl-libui'
@@ -6444,15 +6793,15 @@ require 'glimmer-dsl-libui'
6444
6793
  class FormTable
6445
6794
  include Glimmer
6446
6795
 
6447
- attr_accessor :name, :email, :phone, :city, :state, :filter_value
6796
+ attr_accessor :data, :name, :email, :phone, :city, :state, :filter_value
6448
6797
 
6449
6798
  def initialize
6450
6799
  @data = [
6451
- ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
6452
- ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
6453
- ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
6454
- ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
6455
- ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
6800
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],
6801
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],
6802
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],
6803
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],
6804
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
6456
6805
  ]
6457
6806
  end
6458
6807
 
@@ -6466,7 +6815,7 @@ class FormTable
6466
6815
 
6467
6816
  entry {
6468
6817
  label 'Name'
6469
- text <=> [self, :name]
6818
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
6470
6819
  }
6471
6820
 
6472
6821
  entry {
@@ -6498,8 +6847,8 @@ class FormTable
6498
6847
  if new_row.include?('')
6499
6848
  msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6500
6849
  else
6501
- @data << new_row # automatically inserts a row into the table due to implicit data-binding
6502
- @unfiltered_data = @data.dup
6850
+ data << new_row # automatically inserts a row into the table due to implicit data-binding
6851
+ @unfiltered_data = data.dup
6503
6852
  self.name = '' # automatically clears name entry through explicit data-binding
6504
6853
  self.email = ''
6505
6854
  self.phone = ''
@@ -6511,14 +6860,15 @@ class FormTable
6511
6860
 
6512
6861
  search_entry {
6513
6862
  stretchy false
6514
- text <=> [self, :filter_value, # bidirectional data-binding of text to self.filter_value with after_write option
6863
+ # bidirectional data-binding of text to self.filter_value with after_write option
6864
+ text <=> [self, :filter_value,
6515
6865
  after_write: ->(filter_value) { # execute after write to self.filter_value
6516
- @unfiltered_data ||= @data.dup
6866
+ @unfiltered_data ||= data.dup
6517
6867
  # Unfilter first to remove any previous filters
6518
- @data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
6868
+ data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
6519
6869
  # Now, apply filter if entered
6520
6870
  unless filter_value.empty?
6521
- @data.filter! do |row_data| # affects table indirectly through implicit data-binding
6871
+ data.filter! do |row_data| # affects table indirectly through implicit data-binding
6522
6872
  row_data.any? do |cell|
6523
6873
  cell.to_s.downcase.include?(filter_value.downcase)
6524
6874
  end
@@ -6534,8 +6884,9 @@ class FormTable
6534
6884
  text_column('Phone')
6535
6885
  text_column('City')
6536
6886
  text_column('State')
6537
-
6538
- cell_rows @data # implicit data-binding
6887
+
6888
+ editable true
6889
+ cell_rows <=> [self, :data] # explicit data-binding to raw data Array of Arrays
6539
6890
 
6540
6891
  on_changed do |row, type, row_data|
6541
6892
  puts "Row #{row} #{type}: #{row_data}"
@@ -6549,7 +6900,7 @@ end
6549
6900
  FormTable.new.launch
6550
6901
  ```
6551
6902
 
6552
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
6903
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 5 (with implicit [data-binding](#data-binding)):
6553
6904
 
6554
6905
  ```ruby
6555
6906
  require 'glimmer-dsl-libui'
@@ -6557,11 +6908,11 @@ require 'glimmer-dsl-libui'
6557
6908
  include Glimmer
6558
6909
 
6559
6910
  data = [
6560
- ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
6561
- ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
6562
- ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
6563
- ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
6564
- ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
6911
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],
6912
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],
6913
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],
6914
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],
6915
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
6565
6916
  ]
6566
6917
 
6567
6918
  window('Contacts', 600, 600) { |w|
@@ -6637,7 +6988,8 @@ window('Contacts', 600, 600) { |w|
6637
6988
  text_column('City')
6638
6989
  text_column('State')
6639
6990
 
6640
- cell_rows data # implicit data-binding
6991
+ editable true
6992
+ cell_rows data # implicit data-binding to raw data Array of Arrays
6641
6993
 
6642
6994
  on_changed do |row, type, row_data|
6643
6995
  puts "Row #{row} #{type}: #{row_data}"
@@ -7965,7 +8317,7 @@ class TinyMidiPlayer
7965
8317
  }
7966
8318
  }
7967
8319
 
7968
- combobox { |c|
8320
+ combobox {
7969
8321
  items @midi_files.map { |path| File.basename(path) }
7970
8322
  # data-bind selected item (String) to self.selected_file with on-read/on-write converters and after_write operation
7971
8323
  selected_item <=> [self, :selected_file, on_read: ->(f) {File.basename(f.to_s)}, on_write: ->(f) {File.join(@music_directory, f)}, after_write: -> { play_midi if @th&.alive? }]
@@ -8051,7 +8403,7 @@ class TinyMidiPlayer
8051
8403
  }
8052
8404
  }
8053
8405
 
8054
- combobox { |c|
8406
+ combobox {
8055
8407
  items @midi_files.map { |path| File.basename(path) }
8056
8408
  # data-bind selected index (Integer) to self.selected_file with on-read/on-write converters and after_write operation
8057
8409
  selected <=> [self, :selected_file, on_read: ->(f) {@midi_files.index(f)}, on_write: ->(i) {@midi_files[i]}, after_write: -> { play_midi if @th&.alive? }]
@@ -8173,7 +8525,7 @@ Mac | Windows | Linux
8173
8525
  ----|---------|------
8174
8526
  ![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)
8175
8527
 
8176
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
8528
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
8177
8529
 
8178
8530
  ```ruby
8179
8531
  require 'glimmer-dsl-libui'
@@ -8190,6 +8542,7 @@ class Snake
8190
8542
  @game = Model::Game.new
8191
8543
  @grid = Presenter::Grid.new(@game)
8192
8544
  @game.start
8545
+ @keypress_queue = []
8193
8546
  create_gui
8194
8547
  register_observers
8195
8548
  end
@@ -8209,14 +8562,30 @@ class Snake
8209
8562
  end
8210
8563
 
8211
8564
  Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
8212
- @game.snake.move unless @game.over?
8565
+ unless @game.over?
8566
+ process_queued_keypress
8567
+ @game.snake.move
8568
+ end
8569
+ end
8570
+ end
8571
+
8572
+ def process_queued_keypress
8573
+ # 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)
8574
+ key = @keypress_queue.shift
8575
+ case [@game.snake.head.orientation, key]
8576
+ in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
8577
+ @game.snake.turn_right
8578
+ in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
8579
+ @game.snake.turn_left
8580
+ else
8581
+ # No Op
8213
8582
  end
8214
8583
  end
8215
8584
 
8216
8585
  def create_gui
8217
8586
  @main_window = window {
8218
8587
  # data-bind window title to game score, converting it to a title string on read from the model
8219
- title <= [@game, :score, on_read: -> (score) {"Glimmer Snake (Score: #{@game.score})"}]
8588
+ title <= [@game, :score, on_read: -> (score) {"Snake (Score: #{@game.score})"}]
8220
8589
  content_size @game.width * CELL_SIZE, @game.height * CELL_SIZE
8221
8590
  resizable false
8222
8591
 
@@ -8234,15 +8603,109 @@ class Snake
8234
8603
  }
8235
8604
 
8236
8605
  on_key_up do |area_key_event|
8237
- orientation_and_key = [@game.snake.head.orientation, area_key_event[:ext_key]]
8238
- case orientation_and_key
8239
- in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
8240
- @game.snake.turn_right
8241
- in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
8242
- @game.snake.turn_left
8243
- else
8244
- # No Op
8245
- end
8606
+ @keypress_queue << area_key_event[:ext_key]
8607
+ end
8608
+ }
8609
+ end
8610
+ }
8611
+ end
8612
+ }
8613
+ }
8614
+ end
8615
+ end
8616
+
8617
+ Snake.new.launch
8618
+ ```
8619
+
8620
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
8621
+
8622
+ ```ruby
8623
+ require 'glimmer-dsl-libui'
8624
+
8625
+ require_relative 'snake/presenter/grid'
8626
+
8627
+ class Snake
8628
+ include Glimmer
8629
+
8630
+ CELL_SIZE = 15
8631
+ SNAKE_MOVE_DELAY = 0.1
8632
+
8633
+ def initialize
8634
+ @game = Model::Game.new
8635
+ @grid = Presenter::Grid.new(@game)
8636
+ @game.start
8637
+ @keypress_queue = []
8638
+ create_gui
8639
+ register_observers
8640
+ end
8641
+
8642
+ def launch
8643
+ @main_window.show
8644
+ end
8645
+
8646
+ def register_observers
8647
+ @game.height.times do |row|
8648
+ @game.width.times do |column|
8649
+ observe(@grid.cells[row][column], :color) do |new_color|
8650
+ @cell_grid[row][column].fill = new_color
8651
+ end
8652
+ end
8653
+ end
8654
+
8655
+ observe(@game, :over) do |game_over|
8656
+ Glimmer::LibUI.queue_main do
8657
+ if game_over
8658
+ msg_box('Game Over!', "Score: #{@game.score} | High Score: #{@game.high_score}")
8659
+ @game.start
8660
+ end
8661
+ end
8662
+ end
8663
+
8664
+ Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
8665
+ unless @game.over?
8666
+ process_queued_keypress
8667
+ @game.snake.move
8668
+ end
8669
+ end
8670
+ end
8671
+
8672
+ def process_queued_keypress
8673
+ # 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)
8674
+ key = @keypress_queue.shift
8675
+ case [@game.snake.head.orientation, key]
8676
+ in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
8677
+ @game.snake.turn_right
8678
+ in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
8679
+ @game.snake.turn_left
8680
+ else
8681
+ # No Op
8682
+ end
8683
+ end
8684
+
8685
+ def create_gui
8686
+ @cell_grid = []
8687
+ @main_window = window {
8688
+ # data-bind window title to game score, converting it to a title string on read from the model
8689
+ title <= [@game, :score, on_read: -> (score) {"Snake (Score: #{@game.score})"}]
8690
+ content_size @game.width * CELL_SIZE, @game.height * CELL_SIZE
8691
+ resizable false
8692
+
8693
+ vertical_box {
8694
+ padded false
8695
+
8696
+ @game.height.times do |row|
8697
+ @cell_grid << []
8698
+ horizontal_box {
8699
+ padded false
8700
+
8701
+ @game.width.times do |column|
8702
+ area {
8703
+ @cell_grid.last << square(0, 0, CELL_SIZE) {
8704
+ fill Presenter::Cell::COLOR_CLEAR
8705
+ }
8706
+
8707
+ on_key_up do |area_key_event|
8708
+ @keypress_queue << area_key_event[:ext_key]
8246
8709
  end
8247
8710
  }
8248
8711
  end
@@ -8392,22 +8855,23 @@ class Tetris
8392
8855
  menu('Game') {
8393
8856
  @pause_menu_item = check_menu_item('Pause') {
8394
8857
  enabled false
8395
-
8396
- on_clicked do
8397
- @game.paused = @pause_menu_item.checked?
8398
- end
8858
+ checked <=> [@game, :paused]
8399
8859
  }
8860
+
8400
8861
  menu_item('Restart') {
8401
8862
  on_clicked do
8402
8863
  @game.restart!
8403
8864
  end
8404
8865
  }
8866
+
8405
8867
  separator_menu_item
8868
+
8406
8869
  menu_item('Exit') {
8407
8870
  on_clicked do
8408
8871
  exit(0)
8409
8872
  end
8410
8873
  }
8874
+
8411
8875
  quit_menu_item if OS.mac?
8412
8876
  }
8413
8877
 
@@ -8417,6 +8881,7 @@ class Tetris
8417
8881
  show_high_scores
8418
8882
  end
8419
8883
  }
8884
+
8420
8885
  menu_item('Clear High Scores') {
8421
8886
  on_clicked {
8422
8887
  @game.clear_high_scores!
@@ -8425,22 +8890,16 @@ class Tetris
8425
8890
  }
8426
8891
 
8427
8892
  menu('Options') {
8428
- radio_menu_item('Instant Down on Up Arrow') {
8429
- on_clicked do
8430
- @game.instant_down_on_up = true
8431
- end
8893
+ radio_menu_item('Instant Down on Up Arrow') { |r|
8894
+ checked <=> [@game, :instant_down_on_up]
8432
8895
  }
8433
- radio_menu_item('Rotate Right on Up Arrow') {
8434
- on_clicked do
8435
- @game.rotate_right_on_up = true
8436
- end
8896
+
8897
+ radio_menu_item('Rotate Right on Up Arrow') { |r|
8898
+ checked <=> [@game, :rotate_right_on_up]
8437
8899
  }
8438
- radio_menu_item('Rotate Left on Up Arrow') {
8439
- checked true
8440
-
8441
- on_clicked do
8442
- @game.rotate_left_on_up = true
8443
- end
8900
+
8901
+ radio_menu_item('Rotate Left on Up Arrow') { |r|
8902
+ checked <=> [@game, :rotate_left_on_up]
8444
8903
  }
8445
8904
  }
8446
8905
 
@@ -8452,6 +8911,7 @@ class Tetris
8452
8911
  end
8453
8912
  }
8454
8913
  end
8914
+
8455
8915
  menu_item('About') {
8456
8916
  on_clicked do
8457
8917
  show_about_dialog
@@ -8674,7 +9134,7 @@ Mac | Windows | Linux
8674
9134
  ----|---------|------
8675
9135
  ![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)
8676
9136
 
8677
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
9137
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
8678
9138
 
8679
9139
  ```ruby
8680
9140
  require 'glimmer-dsl-libui'
@@ -8720,6 +9180,7 @@ class TicTacToe
8720
9180
  text(23, 19) {
8721
9181
  string {
8722
9182
  font family: 'Arial', size: OS.mac? ? 20 : 16
9183
+ # data-bind string property of area text attributed string to tic tac toe board cell sign
8723
9184
  string <= [@tic_tac_toe_board[row + 1, column + 1], :sign] # board model is 1-based
8724
9185
  }
8725
9186
  }
@@ -8753,6 +9214,95 @@ end
8753
9214
  TicTacToe.new.launch
8754
9215
  ```
8755
9216
 
9217
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
9218
+
9219
+ ```ruby
9220
+
9221
+ require 'glimmer-dsl-libui'
9222
+
9223
+ require_relative "tic_tac_toe/board"
9224
+
9225
+ class TicTacToe
9226
+ include Glimmer
9227
+
9228
+ def initialize
9229
+ @tic_tac_toe_board = Board.new
9230
+ end
9231
+
9232
+ def launch
9233
+ create_gui
9234
+ register_observers
9235
+ @main_window.show
9236
+ end
9237
+
9238
+ def register_observers
9239
+ observe(@tic_tac_toe_board, :game_status) do |game_status|
9240
+ display_win_message if game_status == Board::WIN
9241
+ display_draw_message if game_status == Board::DRAW
9242
+ end
9243
+
9244
+ 3.times.map do |row|
9245
+ 3.times.map do |column|
9246
+ observe(@tic_tac_toe_board[row + 1, column + 1], :sign) do |sign| # board model is 1-based
9247
+ @cells[row][column].string = sign
9248
+ end
9249
+ end
9250
+ end
9251
+ end
9252
+
9253
+ def create_gui
9254
+ @main_window = window('Tic-Tac-Toe', 180, 180) {
9255
+ resizable false
9256
+
9257
+ @cells = []
9258
+ vertical_box {
9259
+ padded false
9260
+
9261
+ 3.times.map do |row|
9262
+ @cells << []
9263
+ horizontal_box {
9264
+ padded false
9265
+
9266
+ 3.times.map do |column|
9267
+ area {
9268
+ square(0, 0, 60) {
9269
+ stroke :black, thickness: 2
9270
+ }
9271
+ text(23, 19) {
9272
+ @cells[row] << string('') {
9273
+ font family: 'Arial', size: OS.mac? ? 20 : 16
9274
+ }
9275
+ }
9276
+ on_mouse_up do
9277
+ @tic_tac_toe_board.mark(row + 1, column + 1) # board model is 1-based
9278
+ end
9279
+ }
9280
+ end
9281
+ }
9282
+ end
9283
+ }
9284
+ }
9285
+ end
9286
+
9287
+ def display_win_message
9288
+ display_game_over_message("Player #{@tic_tac_toe_board.winning_sign} has won!")
9289
+ end
9290
+
9291
+ def display_draw_message
9292
+ display_game_over_message("Draw!")
9293
+ end
9294
+
9295
+ def display_game_over_message(message_text)
9296
+ Glimmer::LibUI.queue_main do
9297
+ msg_box('Game Over', message_text)
9298
+ @tic_tac_toe_board.reset!
9299
+ end
9300
+ end
9301
+ end
9302
+
9303
+ TicTacToe.new.launch
9304
+ ```
9305
+
8756
9306
  #### Timer
8757
9307
 
8758
9308
  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).
@@ -9113,7 +9663,7 @@ These features have been planned or suggested. You might see them in a future ve
9113
9663
  is fine, but please isolate to its own commit so I can cherry-pick
9114
9664
  around it.
9115
9665
 
9116
- Note that the latest development sometimes takes place in [development](https://github.com/AndyObtiva/glimmer-dsl-libui/tree/development) branch (which is deleted once merged back to [master](https://github.com/AndyObtiva/glimmer-dsl-libui)).
9666
+ Note that the latest development sometimes takes place in the [development](https://github.com/AndyObtiva/glimmer-dsl-libui/tree/development) branch (usually deleted once merged back to [master](https://github.com/AndyObtiva/glimmer-dsl-libui)).
9117
9667
 
9118
9668
  ## Contributors
9119
9669