glimmer-dsl-libui 0.4.9 → 0.4.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/README.md +1330 -487
  4. data/VERSION +1 -1
  5. data/examples/basic_table_button.rb +54 -30
  6. data/examples/basic_table_button2.rb +34 -0
  7. data/examples/basic_table_color.rb +104 -26
  8. data/examples/basic_table_color2.rb +2 -14
  9. data/examples/basic_table_color3.rb +37 -0
  10. data/examples/basic_table_image.rb +1 -1
  11. data/examples/basic_table_image2.rb +2 -14
  12. data/examples/basic_table_image3.rb +44 -0
  13. data/examples/basic_table_image_text.rb +1 -2
  14. data/examples/basic_table_image_text2.rb +2 -13
  15. data/examples/basic_table_image_text3.rb +44 -0
  16. data/examples/cpu_percentage.rb +36 -0
  17. data/examples/editable_table.rb +1 -1
  18. data/examples/form_table.rb +21 -17
  19. data/examples/form_table2.rb +104 -85
  20. data/examples/form_table3.rb +113 -0
  21. data/examples/form_table4.rb +110 -0
  22. data/examples/form_table5.rb +94 -0
  23. data/examples/meta_example.rb +6 -4
  24. data/examples/snake2.rb +97 -0
  25. data/examples/tic_tac_toe.rb +1 -0
  26. data/examples/tic_tac_toe2.rb +84 -0
  27. data/glimmer-dsl-libui.gemspec +0 -0
  28. data/lib/glimmer/dsl/libui/control_expression.rb +2 -1
  29. data/lib/glimmer/dsl/libui/shape_expression.rb +2 -2
  30. data/lib/glimmer/dsl/libui/string_expression.rb +2 -1
  31. data/lib/glimmer/libui/attributed_string.rb +3 -2
  32. data/lib/glimmer/libui/control_proxy/column/background_color_column_proxy.rb +4 -0
  33. data/lib/glimmer/libui/control_proxy/image_proxy.rb +16 -0
  34. data/lib/glimmer/libui/control_proxy/table_proxy.rb +95 -29
  35. data/lib/glimmer/libui/control_proxy.rb +4 -2
  36. data/lib/glimmer/libui/data_bindable.rb +8 -3
  37. data/lib/glimmer/libui/shape.rb +3 -2
  38. data/lib/glimmer/libui.rb +2 -2
  39. data/lib/glimmer-dsl-libui.rb +1 -0
  40. metadata +12 -2
@@ -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
  }