glimmer-dsl-libui 0.4.9 → 0.4.13

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/README.md +1330 -487
  4. data/VERSION +1 -1
  5. data/examples/basic_table_button.rb +54 -30
  6. data/examples/basic_table_button2.rb +34 -0
  7. data/examples/basic_table_color.rb +104 -26
  8. data/examples/basic_table_color2.rb +2 -14
  9. data/examples/basic_table_color3.rb +37 -0
  10. data/examples/basic_table_image.rb +1 -1
  11. data/examples/basic_table_image2.rb +2 -14
  12. data/examples/basic_table_image3.rb +44 -0
  13. data/examples/basic_table_image_text.rb +1 -2
  14. data/examples/basic_table_image_text2.rb +2 -13
  15. data/examples/basic_table_image_text3.rb +44 -0
  16. data/examples/cpu_percentage.rb +36 -0
  17. data/examples/editable_table.rb +1 -1
  18. data/examples/form_table.rb +21 -17
  19. data/examples/form_table2.rb +104 -85
  20. data/examples/form_table3.rb +113 -0
  21. data/examples/form_table4.rb +110 -0
  22. data/examples/form_table5.rb +94 -0
  23. data/examples/meta_example.rb +6 -4
  24. data/examples/snake2.rb +97 -0
  25. data/examples/tic_tac_toe.rb +1 -0
  26. data/examples/tic_tac_toe2.rb +84 -0
  27. data/glimmer-dsl-libui.gemspec +0 -0
  28. data/lib/glimmer/dsl/libui/control_expression.rb +2 -1
  29. data/lib/glimmer/dsl/libui/shape_expression.rb +2 -2
  30. data/lib/glimmer/dsl/libui/string_expression.rb +2 -1
  31. data/lib/glimmer/libui/attributed_string.rb +3 -2
  32. data/lib/glimmer/libui/control_proxy/column/background_color_column_proxy.rb +4 -0
  33. data/lib/glimmer/libui/control_proxy/image_proxy.rb +16 -0
  34. data/lib/glimmer/libui/control_proxy/table_proxy.rb +95 -29
  35. data/lib/glimmer/libui/control_proxy.rb +4 -2
  36. data/lib/glimmer/libui/data_bindable.rb +8 -3
  37. data/lib/glimmer/libui/shape.rb +3 -2
  38. data/lib/glimmer/libui.rb +2 -2
  39. data/lib/glimmer-dsl-libui.rb +1 -0
  40. metadata +12 -2
@@ -1,93 +1,112 @@
1
1
  require 'glimmer-dsl-libui'
2
2
 
3
- include Glimmer
4
-
5
- data = [
6
- ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
7
- ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
8
- ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
9
- ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
10
- ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
11
- ]
12
-
13
- window('Contacts', 600, 600) { |w|
14
- margined true
3
+ class FormTable
4
+ Contact = Struct.new(:name, :email, :phone, :city, :state)
15
5
 
16
- vertical_box {
17
- form {
18
- stretchy false
19
-
20
- @name_entry = entry {
21
- label 'Name'
22
- }
23
-
24
- @email_entry = entry {
25
- label 'Email'
26
- }
27
-
28
- @phone_entry = entry {
29
- label 'Phone'
30
- }
31
-
32
- @city_entry = entry {
33
- label 'City'
34
- }
35
-
36
- @state_entry = entry {
37
- label 'State'
38
- }
39
- }
40
-
41
- button('Save Contact') {
42
- stretchy false
43
-
44
- on_clicked do
45
- new_row = [@name_entry.text, @email_entry.text, @phone_entry.text, @city_entry.text, @state_entry.text]
46
- if new_row.include?('')
47
- msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
48
- else
49
- data << new_row # automatically inserts a row into the table due to implicit data-binding
50
- @unfiltered_data = data.dup
51
- @name_entry.text = ''
52
- @email_entry.text = ''
53
- @phone_entry.text = ''
54
- @city_entry.text = ''
55
- @state_entry.text = ''
56
- end
57
- end
58
- }
59
-
60
- search_entry { |se|
61
- stretchy false
6
+ include Glimmer
7
+
8
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
9
+
10
+ def initialize
11
+ @contacts = [
12
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
13
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
14
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
15
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
16
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
17
+ ]
18
+ end
19
+
20
+ def launch
21
+ window('Contacts', 600, 600) { |w|
22
+ margined true
62
23
 
63
- on_changed do
64
- filter_value = se.text
65
- @unfiltered_data ||= data.dup
66
- # Unfilter first to remove any previous filters
67
- data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
68
- # Now, apply filter if entered
69
- unless filter_value.empty?
70
- data.filter! do |row_data| # affects table indirectly through implicit data-binding
71
- row_data.any? do |cell|
72
- cell.to_s.downcase.include?(filter_value.downcase)
24
+ vertical_box {
25
+ form {
26
+ stretchy false
27
+
28
+ entry {
29
+ label 'Name'
30
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
31
+ }
32
+
33
+ entry {
34
+ label 'Email'
35
+ text <=> [self, :email]
36
+ }
37
+
38
+ entry {
39
+ label 'Phone'
40
+ text <=> [self, :phone]
41
+ }
42
+
43
+ entry {
44
+ label 'City'
45
+ text <=> [self, :city]
46
+ }
47
+
48
+ entry {
49
+ label 'State'
50
+ text <=> [self, :state]
51
+ }
52
+ }
53
+
54
+ button('Save Contact') {
55
+ stretchy false
56
+
57
+ on_clicked do
58
+ new_row = [name, email, phone, city, state]
59
+ if new_row.include?('')
60
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
61
+ else
62
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to implicit data-binding
63
+ @unfiltered_contacts = @contacts.dup
64
+ self.name = '' # automatically clears name entry through explicit data-binding
65
+ self.email = ''
66
+ self.phone = ''
67
+ self.city = ''
68
+ self.state = ''
73
69
  end
74
70
  end
75
- end
76
- end
77
- }
71
+ }
72
+
73
+ search_entry {
74
+ stretchy false
75
+ # bidirectional data-binding of text to self.filter_value with after_write option
76
+ text <=> [self, :filter_value,
77
+ after_write: ->(filter_value) { # execute after write to self.filter_value
78
+ @unfiltered_contacts ||= @contacts.dup
79
+ # Unfilter first to remove any previous filters
80
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
81
+ # Now, apply filter if entered
82
+ unless filter_value.empty?
83
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
84
+ contact.members.any? do |attribute|
85
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
86
+ end
87
+ end
88
+ end
89
+ }
90
+ ]
91
+ }
92
+
93
+ table {
94
+ text_column('Name')
95
+ text_column('Email')
96
+ text_column('Phone')
97
+ text_column('City')
98
+ text_column('State/Province')
78
99
 
79
- table {
80
- text_column('Name')
81
- text_column('Email')
82
- text_column('Phone')
83
- text_column('City')
84
- text_column('State')
100
+ editable true
101
+ cell_rows <=> [self, :contacts, column_attributes: {'State/Province' => :state}] # explicit data-binding to Model Array with column_attributes mapping for a specific column
102
+
103
+ on_changed do |row, type, row_data|
104
+ puts "Row #{row} #{type}: #{row_data}"
105
+ end
106
+ }
107
+ }
108
+ }.show
109
+ end
110
+ end
85
111
 
86
- cell_rows data # implicit data-binding
87
-
88
- on_changed do |row, type, row_data|
89
- puts "Row #{row} #{type}: #{row_data}"
90
- end
91
- }
92
- }
93
- }.show
112
+ FormTable.new.launch
@@ -0,0 +1,113 @@
1
+
2
+ require 'glimmer-dsl-libui'
3
+
4
+ class FormTable
5
+ Contact = Struct.new(:full_name, :email_address, :phone_number, :city_or_town, :state_or_province)
6
+
7
+ include Glimmer
8
+
9
+ attr_accessor :contacts, :name, :email, :phone, :city, :state, :filter_value
10
+
11
+ def initialize
12
+ @contacts = [
13
+ Contact.new('Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'),
14
+ Contact.new('Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'),
15
+ Contact.new('Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'),
16
+ Contact.new('Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'),
17
+ Contact.new('Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'),
18
+ ]
19
+ end
20
+
21
+ def launch
22
+ window('Contacts', 600, 600) { |w|
23
+ margined true
24
+
25
+ vertical_box {
26
+ form {
27
+ stretchy false
28
+
29
+ entry {
30
+ label 'Name'
31
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
32
+ }
33
+
34
+ entry {
35
+ label 'Email'
36
+ text <=> [self, :email]
37
+ }
38
+
39
+ entry {
40
+ label 'Phone'
41
+ text <=> [self, :phone]
42
+ }
43
+
44
+ entry {
45
+ label 'City'
46
+ text <=> [self, :city]
47
+ }
48
+
49
+ entry {
50
+ label 'State'
51
+ text <=> [self, :state]
52
+ }
53
+ }
54
+
55
+ button('Save Contact') {
56
+ stretchy false
57
+
58
+ on_clicked do
59
+ new_row = [name, email, phone, city, state]
60
+ if new_row.include?('')
61
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
62
+ else
63
+ @contacts << Contact.new(*new_row) # automatically inserts a row into the table due to implicit data-binding
64
+ @unfiltered_contacts = @contacts.dup
65
+ self.name = '' # automatically clears name entry through explicit data-binding
66
+ self.email = ''
67
+ self.phone = ''
68
+ self.city = ''
69
+ self.state = ''
70
+ end
71
+ end
72
+ }
73
+
74
+ search_entry {
75
+ stretchy false
76
+ # bidirectional data-binding of text to self.filter_value with after_write option
77
+ text <=> [self, :filter_value,
78
+ after_write: ->(filter_value) { # execute after write to self.filter_value
79
+ @unfiltered_contacts ||= @contacts.dup
80
+ # Unfilter first to remove any previous filters
81
+ self.contacts = @unfiltered_contacts.dup # affects table indirectly through explicit data-binding
82
+ # Now, apply filter if entered
83
+ unless filter_value.empty?
84
+ self.contacts = @contacts.filter do |contact| # affects table indirectly through explicit data-binding
85
+ contact.members.any? do |attribute|
86
+ contact[attribute].to_s.downcase.include?(filter_value.downcase)
87
+ end
88
+ end
89
+ end
90
+ }
91
+ ]
92
+ }
93
+
94
+ table {
95
+ text_column('Name')
96
+ text_column('Email')
97
+ text_column('Phone')
98
+ text_column('City')
99
+ text_column('State')
100
+
101
+ editable true
102
+ 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
103
+
104
+ on_changed do |row, type, row_data|
105
+ puts "Row #{row} #{type}: #{row_data}"
106
+ end
107
+ }
108
+ }
109
+ }.show
110
+ end
111
+ end
112
+
113
+ FormTable.new.launch
@@ -0,0 +1,110 @@
1
+ require 'glimmer-dsl-libui'
2
+
3
+ class FormTable
4
+ include Glimmer
5
+
6
+ attr_accessor :data, :name, :email, :phone, :city, :state, :filter_value
7
+
8
+ def initialize
9
+ @data = [
10
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],
11
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],
12
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],
13
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],
14
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
15
+ ]
16
+ end
17
+
18
+ def launch
19
+ window('Contacts', 600, 600) { |w|
20
+ margined true
21
+
22
+ vertical_box {
23
+ form {
24
+ stretchy false
25
+
26
+ entry {
27
+ label 'Name'
28
+ text <=> [self, :name] # bidirectional data-binding between entry text and self.name
29
+ }
30
+
31
+ entry {
32
+ label 'Email'
33
+ text <=> [self, :email]
34
+ }
35
+
36
+ entry {
37
+ label 'Phone'
38
+ text <=> [self, :phone]
39
+ }
40
+
41
+ entry {
42
+ label 'City'
43
+ text <=> [self, :city]
44
+ }
45
+
46
+ entry {
47
+ label 'State'
48
+ text <=> [self, :state]
49
+ }
50
+ }
51
+
52
+ button('Save Contact') {
53
+ stretchy false
54
+
55
+ on_clicked do
56
+ new_row = [name, email, phone, city, state]
57
+ if new_row.include?('')
58
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
59
+ else
60
+ data << new_row # automatically inserts a row into the table due to implicit data-binding
61
+ @unfiltered_data = data.dup
62
+ self.name = '' # automatically clears name entry through explicit data-binding
63
+ self.email = ''
64
+ self.phone = ''
65
+ self.city = ''
66
+ self.state = ''
67
+ end
68
+ end
69
+ }
70
+
71
+ search_entry {
72
+ stretchy false
73
+ # bidirectional data-binding of text to self.filter_value with after_write option
74
+ text <=> [self, :filter_value,
75
+ after_write: ->(filter_value) { # execute after write to self.filter_value
76
+ @unfiltered_data ||= data.dup
77
+ # Unfilter first to remove any previous filters
78
+ data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
79
+ # Now, apply filter if entered
80
+ unless filter_value.empty?
81
+ data.filter! do |row_data| # affects table indirectly through implicit data-binding
82
+ row_data.any? do |cell|
83
+ cell.to_s.downcase.include?(filter_value.downcase)
84
+ end
85
+ end
86
+ end
87
+ }
88
+ ]
89
+ }
90
+
91
+ table {
92
+ text_column('Name')
93
+ text_column('Email')
94
+ text_column('Phone')
95
+ text_column('City')
96
+ text_column('State')
97
+
98
+ editable true
99
+ cell_rows <=> [self, :data] # explicit data-binding to raw data Array of Arrays
100
+
101
+ on_changed do |row, type, row_data|
102
+ puts "Row #{row} #{type}: #{row_data}"
103
+ end
104
+ }
105
+ }
106
+ }.show
107
+ end
108
+ end
109
+
110
+ FormTable.new.launch
@@ -0,0 +1,94 @@
1
+ require 'glimmer-dsl-libui'
2
+
3
+ include Glimmer
4
+
5
+ data = [
6
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO'],
7
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA'],
8
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL'],
9
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA'],
10
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA'],
11
+ ]
12
+
13
+ window('Contacts', 600, 600) { |w|
14
+ margined true
15
+
16
+ vertical_box {
17
+ form {
18
+ stretchy false
19
+
20
+ @name_entry = entry {
21
+ label 'Name'
22
+ }
23
+
24
+ @email_entry = entry {
25
+ label 'Email'
26
+ }
27
+
28
+ @phone_entry = entry {
29
+ label 'Phone'
30
+ }
31
+
32
+ @city_entry = entry {
33
+ label 'City'
34
+ }
35
+
36
+ @state_entry = entry {
37
+ label 'State'
38
+ }
39
+ }
40
+
41
+ button('Save Contact') {
42
+ stretchy false
43
+
44
+ on_clicked do
45
+ new_row = [@name_entry.text, @email_entry.text, @phone_entry.text, @city_entry.text, @state_entry.text]
46
+ if new_row.include?('')
47
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
48
+ else
49
+ data << new_row # automatically inserts a row into the table due to implicit data-binding
50
+ @unfiltered_data = data.dup
51
+ @name_entry.text = ''
52
+ @email_entry.text = ''
53
+ @phone_entry.text = ''
54
+ @city_entry.text = ''
55
+ @state_entry.text = ''
56
+ end
57
+ end
58
+ }
59
+
60
+ search_entry { |se|
61
+ stretchy false
62
+
63
+ on_changed do
64
+ filter_value = se.text
65
+ @unfiltered_data ||= data.dup
66
+ # Unfilter first to remove any previous filters
67
+ data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
68
+ # Now, apply filter if entered
69
+ unless filter_value.empty?
70
+ data.filter! do |row_data| # affects table indirectly through implicit data-binding
71
+ row_data.any? do |cell|
72
+ cell.to_s.downcase.include?(filter_value.downcase)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ }
78
+
79
+ table {
80
+ text_column('Name')
81
+ text_column('Email')
82
+ text_column('Phone')
83
+ text_column('City')
84
+ text_column('State')
85
+
86
+ editable true
87
+ cell_rows data # implicit data-binding to raw data Array of Arrays
88
+
89
+ on_changed do |row, type, row_data|
90
+ puts "Row #{row} #{type}: #{row_data}"
91
+ end
92
+ }
93
+ }
94
+ }.show
@@ -25,11 +25,11 @@ class MetaExample
25
25
  end
26
26
 
27
27
  def basic_examples
28
- examples.select {|example| example.start_with?('Basic') || ADDITIONAL_BASIC_EXAMPLES.include?(example) }
28
+ examples.select {|example| example.start_with?('Basic') || ADDITIONAL_BASIC_EXAMPLES.include?(example) }.sort
29
29
  end
30
30
 
31
31
  def advanced_examples
32
- examples - basic_examples
32
+ (examples - basic_examples).sort
33
33
  end
34
34
 
35
35
  def examples_with_versions
@@ -69,12 +69,12 @@ class MetaExample
69
69
  command = "#{RbConfig.ruby} -r #{glimmer_dsl_libui_file} #{example} 2>&1"
70
70
  result = ''
71
71
  IO.popen(command) do |f|
72
- sleep(0.0001) # yield to main thread
72
+ sleep(0.00001) # yield to main thread
73
73
  f.each_line do |line|
74
74
  result << line
75
75
  puts line
76
76
  $stdout.flush # for Windows
77
- sleep(0.0001) # yield to main thread
77
+ sleep(0.00001) # yield to main thread
78
78
  end
79
79
  end
80
80
  Glimmer::LibUI.queue_main { msg_box('Error Running Example', result) } if result.downcase.include?('error')
@@ -104,6 +104,7 @@ class MetaExample
104
104
  example = selected_example
105
105
  self.code_text = File.read(file_path_for(example))
106
106
  @version_spinbox.value = 1
107
+ @advanced_example_radio_buttons.selected = -1
107
108
  end
108
109
  }
109
110
 
@@ -124,6 +125,7 @@ class MetaExample
124
125
  example = selected_example
125
126
  self.code_text = File.read(file_path_for(example))
126
127
  @version_spinbox.value = 1
128
+ @basic_example_radio_buttons.selected = -1
127
129
  end
128
130
  }
129
131
 
@@ -0,0 +1,97 @@
1
+ require 'glimmer-dsl-libui'
2
+
3
+ require_relative 'snake/presenter/grid'
4
+
5
+ class Snake
6
+ include Glimmer
7
+
8
+ CELL_SIZE = 15
9
+ SNAKE_MOVE_DELAY = 0.1
10
+
11
+ def initialize
12
+ @game = Model::Game.new
13
+ @grid = Presenter::Grid.new(@game)
14
+ @game.start
15
+ @keypress_queue = []
16
+ create_gui
17
+ register_observers
18
+ end
19
+
20
+ def launch
21
+ @main_window.show
22
+ end
23
+
24
+ def register_observers
25
+ @game.height.times do |row|
26
+ @game.width.times do |column|
27
+ observe(@grid.cells[row][column], :color) do |new_color|
28
+ @cell_grid[row][column].fill = new_color
29
+ end
30
+ end
31
+ end
32
+
33
+ observe(@game, :over) do |game_over|
34
+ Glimmer::LibUI.queue_main do
35
+ if game_over
36
+ msg_box('Game Over!', "Score: #{@game.score} | High Score: #{@game.high_score}")
37
+ @game.start
38
+ end
39
+ end
40
+ end
41
+
42
+ Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
43
+ unless @game.over?
44
+ process_queued_keypress
45
+ @game.snake.move
46
+ end
47
+ end
48
+ end
49
+
50
+ def process_queued_keypress
51
+ # 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)
52
+ key = @keypress_queue.shift
53
+ case [@game.snake.head.orientation, key]
54
+ in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
55
+ @game.snake.turn_right
56
+ in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
57
+ @game.snake.turn_left
58
+ else
59
+ # No Op
60
+ end
61
+ end
62
+
63
+ def create_gui
64
+ @cell_grid = []
65
+ @main_window = window {
66
+ # data-bind window title to game score, converting it to a title string on read from the model
67
+ title <= [@game, :score, on_read: -> (score) {"Snake (Score: #{@game.score})"}]
68
+ content_size @game.width * CELL_SIZE, @game.height * CELL_SIZE
69
+ resizable false
70
+
71
+ vertical_box {
72
+ padded false
73
+
74
+ @game.height.times do |row|
75
+ @cell_grid << []
76
+ horizontal_box {
77
+ padded false
78
+
79
+ @game.width.times do |column|
80
+ area {
81
+ @cell_grid.last << square(0, 0, CELL_SIZE) {
82
+ fill Presenter::Cell::COLOR_CLEAR
83
+ }
84
+
85
+ on_key_up do |area_key_event|
86
+ @keypress_queue << area_key_event[:ext_key]
87
+ end
88
+ }
89
+ end
90
+ }
91
+ end
92
+ }
93
+ }
94
+ end
95
+ end
96
+
97
+ Snake.new.launch
@@ -41,6 +41,7 @@ class TicTacToe
41
41
  text(23, 19) {
42
42
  string {
43
43
  font family: 'Arial', size: OS.mac? ? 20 : 16
44
+ # data-bind string property of area text attributed string to tic tac toe board cell sign
44
45
  string <= [@tic_tac_toe_board[row + 1, column + 1], :sign] # board model is 1-based
45
46
  }
46
47
  }