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
@@ -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
@@ -24,18 +24,28 @@ class MetaExample
24
24
  @examples
25
25
  end
26
26
 
27
+ def basic_examples
28
+ examples.select {|example| example.start_with?('Basic') || ADDITIONAL_BASIC_EXAMPLES.include?(example) }.sort
29
+ end
30
+
31
+ def advanced_examples
32
+ (examples - basic_examples).sort
33
+ end
34
+
27
35
  def examples_with_versions
28
- examples.map do |example|
29
- version_count_for(example) > 1 ? "#{example} (#{version_count_for(example)} versions)" : example
30
- end
36
+ append_versions(examples)
31
37
  end
32
38
 
33
39
  def basic_examples_with_versions
34
- examples_with_versions.select {|example| example.start_with?('Basic') || ADDITIONAL_BASIC_EXAMPLES.include?(example) }
40
+ append_versions(basic_examples)
35
41
  end
36
42
 
37
43
  def advanced_examples_with_versions
38
- examples_with_versions - basic_examples_with_versions
44
+ append_versions(advanced_examples)
45
+ end
46
+
47
+ def append_versions(examples)
48
+ examples.map { |example| version_count_for(example) > 1 ? "#{example} (#{version_count_for(example)} versions)" : example }
39
49
  end
40
50
 
41
51
  def file_path_for(example)
@@ -59,12 +69,12 @@ class MetaExample
59
69
  command = "#{RbConfig.ruby} -r #{glimmer_dsl_libui_file} #{example} 2>&1"
60
70
  result = ''
61
71
  IO.popen(command) do |f|
62
- sleep(0.0001) # yield to main thread
72
+ sleep(0.00001) # yield to main thread
63
73
  f.each_line do |line|
64
74
  result << line
65
75
  puts line
66
76
  $stdout.flush # for Windows
67
- sleep(0.0001) # yield to main thread
77
+ sleep(0.00001) # yield to main thread
68
78
  end
69
79
  end
70
80
  Glimmer::LibUI.queue_main { msg_box('Error Running Example', result) } if result.downcase.include?('error')
@@ -72,7 +82,7 @@ class MetaExample
72
82
  end
73
83
 
74
84
  def launch
75
- window('Meta-Example', 700, 500) {
85
+ window('Meta-Example', 1000, 500) {
76
86
  margined true
77
87
 
78
88
  horizontal_box {
@@ -94,6 +104,7 @@ class MetaExample
94
104
  example = selected_example
95
105
  self.code_text = File.read(file_path_for(example))
96
106
  @version_spinbox.value = 1
107
+ @advanced_example_radio_buttons.selected = -1
97
108
  end
98
109
  }
99
110
 
@@ -107,12 +118,14 @@ class MetaExample
107
118
  @advanced_example_radio_buttons = radio_buttons {
108
119
  stretchy false
109
120
  items advanced_examples_with_versions
121
+ selected -1
110
122
 
111
123
  on_selected do
112
124
  @selected_example_index = examples_with_versions.index(advanced_examples_with_versions[@advanced_example_radio_buttons.selected])
113
125
  example = selected_example
114
126
  self.code_text = File.read(file_path_for(example))
115
127
  @version_spinbox.value = 1
128
+ @basic_example_radio_buttons.selected = -1
116
129
  end
117
130
  }
118
131
 
@@ -70,7 +70,7 @@ class TinyMidiPlayer
70
70
  }
71
71
  }
72
72
 
73
- combobox { |c|
73
+ combobox {
74
74
  items @midi_files.map { |path| File.basename(path) }
75
75
  # data-bind selected item (String) to self.selected_file with on-read/on-write converters and after_write operation
76
76
  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? }]
data/examples/snake.rb CHANGED
@@ -12,6 +12,7 @@ class Snake
12
12
  @game = Model::Game.new
13
13
  @grid = Presenter::Grid.new(@game)
14
14
  @game.start
15
+ @keypress_queue = []
15
16
  create_gui
16
17
  register_observers
17
18
  end
@@ -31,7 +32,23 @@ class Snake
31
32
  end
32
33
 
33
34
  Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
34
- @game.snake.move unless @game.over?
35
+ unless @game.over?
36
+ process_queued_keypress
37
+ @game.snake.move
38
+ end
39
+ end
40
+ end
41
+
42
+ def process_queued_keypress
43
+ # 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)
44
+ key = @keypress_queue.shift
45
+ case [@game.snake.head.orientation, key]
46
+ in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
47
+ @game.snake.turn_right
48
+ in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
49
+ @game.snake.turn_left
50
+ else
51
+ # No Op
35
52
  end
36
53
  end
37
54
 
@@ -56,15 +73,7 @@ class Snake
56
73
  }
57
74
 
58
75
  on_key_up do |area_key_event|
59
- orientation_and_key = [@game.snake.head.orientation, area_key_event[:ext_key]]
60
- case orientation_and_key
61
- in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
62
- @game.snake.turn_right
63
- in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
64
- @game.snake.turn_left
65
- else
66
- # No Op
67
- end
76
+ @keypress_queue << area_key_event[:ext_key]
68
77
  end
69
78
  }
70
79
  end
@@ -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
data/examples/tetris.rb CHANGED
@@ -109,22 +109,23 @@ class Tetris
109
109
  menu('Game') {
110
110
  @pause_menu_item = check_menu_item('Pause') {
111
111
  enabled false
112
-
113
- on_clicked do
114
- @game.paused = @pause_menu_item.checked?
115
- end
112
+ checked <=> [@game, :paused]
116
113
  }
114
+
117
115
  menu_item('Restart') {
118
116
  on_clicked do
119
117
  @game.restart!
120
118
  end
121
119
  }
120
+
122
121
  separator_menu_item
122
+
123
123
  menu_item('Exit') {
124
124
  on_clicked do
125
125
  exit(0)
126
126
  end
127
127
  }
128
+
128
129
  quit_menu_item if OS.mac?
129
130
  }
130
131
 
@@ -134,6 +135,7 @@ class Tetris
134
135
  show_high_scores
135
136
  end
136
137
  }
138
+
137
139
  menu_item('Clear High Scores') {
138
140
  on_clicked {
139
141
  @game.clear_high_scores!
@@ -142,22 +144,16 @@ class Tetris
142
144
  }
143
145
 
144
146
  menu('Options') {
145
- radio_menu_item('Instant Down on Up Arrow') {
146
- on_clicked do
147
- @game.instant_down_on_up = true
148
- end
147
+ radio_menu_item('Instant Down on Up Arrow') { |r|
148
+ checked <=> [@game, :instant_down_on_up]
149
149
  }
150
- radio_menu_item('Rotate Right on Up Arrow') {
151
- on_clicked do
152
- @game.rotate_right_on_up = true
153
- end
150
+
151
+ radio_menu_item('Rotate Right on Up Arrow') { |r|
152
+ checked <=> [@game, :rotate_right_on_up]
154
153
  }
155
- radio_menu_item('Rotate Left on Up Arrow') {
156
- checked true
157
-
158
- on_clicked do
159
- @game.rotate_left_on_up = true
160
- end
154
+
155
+ radio_menu_item('Rotate Left on Up Arrow') { |r|
156
+ checked <=> [@game, :rotate_left_on_up]
161
157
  }
162
158
  }
163
159
 
@@ -169,6 +165,7 @@ class Tetris
169
165
  end
170
166
  }
171
167
  end
168
+
172
169
  menu_item('About') {
173
170
  on_clicked do
174
171
  show_about_dialog
@@ -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
  }
@@ -0,0 +1,84 @@
1
+
2
+ require 'glimmer-dsl-libui'
3
+
4
+ require_relative "tic_tac_toe/board"
5
+
6
+ class TicTacToe
7
+ include Glimmer
8
+
9
+ def initialize
10
+ @tic_tac_toe_board = Board.new
11
+ end
12
+
13
+ def launch
14
+ create_gui
15
+ register_observers
16
+ @main_window.show
17
+ end
18
+
19
+ def register_observers
20
+ observe(@tic_tac_toe_board, :game_status) do |game_status|
21
+ display_win_message if game_status == Board::WIN
22
+ display_draw_message if game_status == Board::DRAW
23
+ end
24
+
25
+ 3.times.map do |row|
26
+ 3.times.map do |column|
27
+ observe(@tic_tac_toe_board[row + 1, column + 1], :sign) do |sign| # board model is 1-based
28
+ @cells[row][column].string = sign
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def create_gui
35
+ @main_window = window('Tic-Tac-Toe', 180, 180) {
36
+ resizable false
37
+
38
+ @cells = []
39
+ vertical_box {
40
+ padded false
41
+
42
+ 3.times.map do |row|
43
+ @cells << []
44
+ horizontal_box {
45
+ padded false
46
+
47
+ 3.times.map do |column|
48
+ area {
49
+ square(0, 0, 60) {
50
+ stroke :black, thickness: 2
51
+ }
52
+ text(23, 19) {
53
+ @cells[row] << string('') {
54
+ font family: 'Arial', size: OS.mac? ? 20 : 16
55
+ }
56
+ }
57
+ on_mouse_up do
58
+ @tic_tac_toe_board.mark(row + 1, column + 1) # board model is 1-based
59
+ end
60
+ }
61
+ end
62
+ }
63
+ end
64
+ }
65
+ }
66
+ end
67
+
68
+ def display_win_message
69
+ display_game_over_message("Player #{@tic_tac_toe_board.winning_sign} has won!")
70
+ end
71
+
72
+ def display_draw_message
73
+ display_game_over_message("Draw!")
74
+ end
75
+
76
+ def display_game_over_message(message_text)
77
+ Glimmer::LibUI.queue_main do
78
+ msg_box('Game Over', message_text)
79
+ @tic_tac_toe_board.reset!
80
+ end
81
+ end
82
+ end
83
+
84
+ TicTacToe.new.launch
Binary file
@@ -39,8 +39,9 @@ module Glimmer
39
39
  end
40
40
 
41
41
  def add_content(parent, keyword, *args, &block)
42
+ options = args.last.is_a?(Hash) ? args.last : {post_add_content: true}
42
43
  super
43
- parent&.post_add_content
44
+ parent&.post_add_content if options[:post_add_content]
44
45
  end
45
46
  end
46
47
  end
@@ -47,10 +47,10 @@ module Glimmer
47
47
  end
48
48
 
49
49
  def add_content(parent, keyword, *args, &block)
50
+ options = args.last.is_a?(Hash) ? args.last : {post_add_content: true}
50
51
  super
51
- parent.post_add_content
52
+ parent&.post_add_content if options[:post_add_content]
52
53
  end
53
-
54
54
  end
55
55
  end
56
56
  end
@@ -46,7 +46,8 @@ module Glimmer
46
46
  end
47
47
 
48
48
  def add_content(parent, keyword, *args, &block)
49
- parent.post_add_content(block)
49
+ options = args.last.is_a?(Hash) ? args.last : {post_add_content: true}
50
+ parent&.post_add_content(block) if options[:post_add_content]
50
51
  end
51
52
  end
52
53
  end
@@ -30,8 +30,9 @@ module Glimmer
30
30
  class AttributedString
31
31
  include DataBindable
32
32
 
33
- attr_reader :keyword, :parent_proxy, :args
33
+ attr_reader :keyword, :parent_proxy, :args, :content_added
34
34
  attr_accessor :block
35
+ alias content_added? content_added
35
36
 
36
37
  def initialize(keyword, parent_proxy, args, &block)
37
38
  @keyword = keyword
@@ -205,7 +206,7 @@ module Glimmer
205
206
  end
206
207
 
207
208
  def content(&block)
208
- Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Libui::StringExpression.new, @keyword, &block)
209
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Libui::StringExpression.new, @keyword, {post_add_content: true}, &block)
209
210
  end
210
211
  end
211
212
  end
@@ -29,6 +29,10 @@ module Glimmer
29
29
  # Follows the Proxy Design Pattern
30
30
  class CheckboxProxy < ControlProxy
31
31
  DEFAULT_TEXT = ''
32
+
33
+ def data_bind_write(property, model_binding)
34
+ handle_listener('on_toggled') { model_binding.call(checked) } if property == 'checked'
35
+ end
32
36
 
33
37
  private
34
38
 
@@ -114,8 +114,7 @@ module Glimmer
114
114
  super
115
115
  end
116
116
 
117
- def data_bind(property, model_binding)
118
- super
117
+ def data_bind_write(property, model_binding)
119
118
  handle_listener('on_changed') { model_binding.call(color) } if property == 'color'
120
119
  end
121
120
  end