glimmer-dsl-libui 0.4.7 → 0.4.11

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