glimmer-dsl-libui 0.2.18 → 0.2.22

Sign up to get free protection for your applications and to get access to all the features.
data/examples/tetris.rb CHANGED
@@ -9,40 +9,55 @@ class Tetris
9
9
  BEVEL_CONSTANT = 20
10
10
  COLOR_GRAY = {r: 192, g: 192, b: 192}
11
11
 
12
- attr_reader :game
13
-
14
12
  def initialize
15
13
  @game = Model::Game.new
16
- create_gui
17
- register_observers
18
14
  end
19
15
 
20
16
  def launch
17
+ create_gui
18
+ register_observers
21
19
  @game.start!
22
20
  @main_window.show
23
21
  end
24
22
 
25
23
  def create_gui
26
- @main_window = window('Glimmer Tetris', Model::Game::PLAYFIELD_WIDTH * BLOCK_SIZE, Model::Game::PLAYFIELD_HEIGHT * BLOCK_SIZE) {
27
- playfield(playfield_width: Model::Game::PLAYFIELD_WIDTH, playfield_height: Model::Game::PLAYFIELD_HEIGHT, block_size: BLOCK_SIZE)
24
+ menu_bar
25
+
26
+ @main_window = window('Glimmer Tetris') {
27
+ content_size Model::Game::PLAYFIELD_WIDTH * BLOCK_SIZE, Model::Game::PLAYFIELD_HEIGHT * BLOCK_SIZE + 98
28
+ resizable false
29
+
30
+ vertical_box {
31
+ label { # filler
32
+ stretchy false
33
+ }
34
+
35
+ score_board(block_size: BLOCK_SIZE) {
36
+ stretchy false
37
+ }
38
+
39
+ @playfield_blocks = playfield(playfield_width: Model::Game::PLAYFIELD_WIDTH, playfield_height: Model::Game::PLAYFIELD_HEIGHT, block_size: BLOCK_SIZE)
40
+ }
28
41
  }
29
42
  end
30
43
 
31
44
  def register_observers
32
45
  Glimmer::DataBinding::Observer.proc do |game_over|
33
46
  if game_over
47
+ @pause_menu_item.enabled = false
34
48
  show_game_over_dialog
35
49
  else
50
+ @pause_menu_item.enabled = true
36
51
  start_moving_tetrominos_down
37
52
  end
38
53
  end.observe(@game, :game_over)
39
54
 
40
55
  Model::Game::PLAYFIELD_HEIGHT.times do |row|
41
- Model::Game::PLAYFIELD_HEIGHT.times do |column|
56
+ Model::Game::PLAYFIELD_WIDTH.times do |column|
42
57
  Glimmer::DataBinding::Observer.proc do |new_color|
43
58
  Glimmer::LibUI.queue_main do
44
59
  color = Glimmer::LibUI.interpret_color(new_color)
45
- block = @blocks[row][column]
60
+ block = @playfield_blocks[row][column]
46
61
  block[:background_square].fill = color
47
62
  block[:top_bevel_edge].fill = {r: color[:r] + 4*BEVEL_CONSTANT, g: color[:g] + 4*BEVEL_CONSTANT, b: color[:b] + 4*BEVEL_CONSTANT}
48
63
  block[:right_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
@@ -53,27 +68,135 @@ class Tetris
53
68
  end.observe(@game.playfield[row][column], :color)
54
69
  end
55
70
  end
71
+
72
+ Model::Game::PREVIEW_PLAYFIELD_HEIGHT.times do |row|
73
+ Model::Game::PREVIEW_PLAYFIELD_WIDTH.times do |column|
74
+ Glimmer::DataBinding::Observer.proc do |new_color|
75
+ Glimmer::LibUI.queue_main do
76
+ color = Glimmer::LibUI.interpret_color(new_color)
77
+ block = @preview_playfield_blocks[row][column]
78
+ block[:background_square].fill = color
79
+ block[:top_bevel_edge].fill = {r: color[:r] + 4*BEVEL_CONSTANT, g: color[:g] + 4*BEVEL_CONSTANT, b: color[:b] + 4*BEVEL_CONSTANT}
80
+ block[:right_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
81
+ block[:bottom_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
82
+ block[:left_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
83
+ block[:border_square].stroke = new_color == Model::Block::COLOR_CLEAR ? COLOR_GRAY : color
84
+ end
85
+ end.observe(@game.preview_playfield[row][column], :color)
86
+ end
87
+ end
88
+
89
+ Glimmer::DataBinding::Observer.proc do |new_score|
90
+ Glimmer::LibUI.queue_main do
91
+ @score_label.text = new_score.to_s
92
+ end
93
+ end.observe(@game, :score)
94
+
95
+ Glimmer::DataBinding::Observer.proc do |new_lines|
96
+ Glimmer::LibUI.queue_main do
97
+ @lines_label.text = new_lines.to_s
98
+ end
99
+ end.observe(@game, :lines)
100
+
101
+ Glimmer::DataBinding::Observer.proc do |new_level|
102
+ Glimmer::LibUI.queue_main do
103
+ @level_label.text = new_level.to_s
104
+ end
105
+ end.observe(@game, :level)
106
+ end
107
+
108
+ def menu_bar
109
+ menu('Game') {
110
+ @pause_menu_item = check_menu_item('Pause') {
111
+ enabled false
112
+
113
+ on_clicked do
114
+ @game.paused = @pause_menu_item.checked?
115
+ end
116
+ }
117
+ menu_item('Restart') {
118
+ on_clicked do
119
+ @game.restart!
120
+ end
121
+ }
122
+ separator_menu_item
123
+ menu_item('Exit') {
124
+ on_clicked do
125
+ exit(0)
126
+ end
127
+ }
128
+ quit_menu_item if OS.mac?
129
+ }
130
+
131
+ menu('View') {
132
+ menu_item('Show High Scores') {
133
+ on_clicked do
134
+ show_high_scores
135
+ end
136
+ }
137
+ menu_item('Clear High Scores') {
138
+ on_clicked {
139
+ @game.clear_high_scores!
140
+ }
141
+ }
142
+ }
143
+
144
+ menu('Options') {
145
+ radio_menu_item('Instant Down on Up Arrow') {
146
+ on_clicked do
147
+ @game.instant_down_on_up = true
148
+ end
149
+ }
150
+ radio_menu_item('Rotate Right on Up Arrow') {
151
+ on_clicked do
152
+ @game.rotate_right_on_up = true
153
+ end
154
+ }
155
+ radio_menu_item('Rotate Left on Up Arrow') {
156
+ on_clicked do
157
+ @game.rotate_left_on_up = true
158
+ end
159
+ }
160
+ }
161
+
162
+ menu('Help') {
163
+ if OS.mac?
164
+ about_menu_item {
165
+ on_clicked do
166
+ show_about_dialog
167
+ end
168
+ }
169
+ end
170
+ menu_item('About') {
171
+ on_clicked do
172
+ show_about_dialog
173
+ end
174
+ }
175
+ }
56
176
  end
57
177
 
58
- def playfield(playfield_width: , playfield_height: , block_size: )
59
- @blocks = []
178
+ def playfield(playfield_width: , playfield_height: , block_size: , &extra_content)
179
+ blocks = []
60
180
  vertical_box {
61
181
  padded false
62
182
 
63
183
  playfield_height.times.map do |row|
64
- @blocks << []
184
+ blocks << []
65
185
  horizontal_box {
66
186
  padded false
67
187
 
68
188
  playfield_width.times.map do |column|
69
- @blocks.last << block(row: row, column: column, block_size: block_size)
189
+ blocks.last << block(row: row, column: column, block_size: block_size)
70
190
  end
71
191
  }
72
192
  end
193
+
194
+ extra_content&.call
73
195
  }
196
+ blocks
74
197
  end
75
198
 
76
- def block(row: , column: , block_size: )
199
+ def block(row: , column: , block_size: , &extra_content)
77
200
  block = {}
78
201
  bevel_pixel_size = 0.16 * block_size.to_f
79
202
  color = Glimmer::LibUI.interpret_color(Model::Block::COLOR_CLEAR)
@@ -112,32 +235,89 @@ class Tetris
112
235
  on_key_down do |key_event|
113
236
  case key_event
114
237
  in ext_key: :down
115
- game.down!
238
+ @game.down!
239
+ in key: ' '
240
+ @game.down!(instant: true)
116
241
  in ext_key: :up
117
- case game.up_arrow_action
242
+ case @game.up_arrow_action
118
243
  when :instant_down
119
- game.down!(instant: true)
244
+ @game.down!(instant: true)
120
245
  when :rotate_right
121
- game.rotate!(:right)
246
+ @game.rotate!(:right)
122
247
  when :rotate_left
123
- game.rotate!(:left)
248
+ @game.rotate!(:left)
124
249
  end
125
250
  in ext_key: :left
126
- game.left!
251
+ @game.left!
127
252
  in ext_key: :right
128
- game.right!
253
+ @game.right!
129
254
  in modifier: :shift
130
- game.rotate!(:right)
255
+ @game.rotate!(:right)
131
256
  in modifier: :control
132
- game.rotate!(:left)
257
+ @game.rotate!(:left)
133
258
  else
134
259
  # Do Nothing
135
260
  end
136
261
  end
262
+
263
+ extra_content&.call
137
264
  }
138
265
  block
139
266
  end
140
267
 
268
+ def score_board(block_size: , &extra_content)
269
+ vertical_box {
270
+ horizontal_box {
271
+ label # filler
272
+ @preview_playfield_blocks = playfield(playfield_width: Model::Game::PREVIEW_PLAYFIELD_WIDTH, playfield_height: Model::Game::PREVIEW_PLAYFIELD_HEIGHT, block_size: block_size)
273
+ label # filler
274
+ }
275
+
276
+ horizontal_box {
277
+ label # filler
278
+ grid {
279
+ stretchy false
280
+
281
+ label('Score') {
282
+ left 0
283
+ top 0
284
+ halign :fill
285
+ }
286
+ @score_label = label {
287
+ left 0
288
+ top 1
289
+ halign :center
290
+ }
291
+
292
+ label('Lines') {
293
+ left 1
294
+ top 0
295
+ halign :fill
296
+ }
297
+ @lines_label = label {
298
+ left 1
299
+ top 1
300
+ halign :center
301
+ }
302
+
303
+ label('Level') {
304
+ left 2
305
+ top 0
306
+ halign :fill
307
+ }
308
+ @level_label = label {
309
+ left 2
310
+ top 1
311
+ halign :center
312
+ }
313
+ }
314
+ label # filler
315
+ }
316
+
317
+ extra_content&.call
318
+ }
319
+ end
320
+
141
321
  def start_moving_tetrominos_down
142
322
  Glimmer::LibUI.timer(@game.delay) do
143
323
  @game.down! if !@game.game_over? && !@game.paused?
@@ -146,10 +326,29 @@ class Tetris
146
326
 
147
327
  def show_game_over_dialog
148
328
  Glimmer::LibUI.queue_main do
149
- msg_box('Game Over', "Score: #{@game.high_scores.first.score}\nLines: #{@game.high_scores.first.lines}\nLevel: #{@game.high_scores.first.level}")
329
+ msg_box('Game Over!', "Score: #{@game.high_scores.first.score}\nLines: #{@game.high_scores.first.lines}\nLevel: #{@game.high_scores.first.level}")
150
330
  @game.restart!
151
331
  end
152
332
  end
333
+
334
+ def show_high_scores
335
+ Glimmer::LibUI.queue_main do
336
+ if @game.high_scores.empty?
337
+ high_scores_string = "No games have been scored yet."
338
+ else
339
+ high_scores_string = @game.high_scores.map do |high_score|
340
+ "#{high_score.name} | Score: #{high_score.score} | Lines: #{high_score.lines} | Level: #{high_score.level}"
341
+ end.join("\n")
342
+ end
343
+ msg_box('High Scores', high_scores_string)
344
+ end
345
+ end
346
+
347
+ def show_about_dialog
348
+ Glimmer::LibUI.queue_main do
349
+ msg_box('About', 'Glimmer Tetris - Glimmer DSL for LibUI Example - Copyright (c) 2021 Andy Maleh')
350
+ end
351
+ end
153
352
  end
154
353
 
155
354
  Tetris.new.launch
@@ -0,0 +1,145 @@
1
+ # Copyright (c) 2007-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require_relative 'cell'
23
+
24
+ class TicTacToe
25
+ class Board
26
+ DRAW = :draw
27
+ IN_PROGRESS = :in_progress
28
+ WIN = :win
29
+ attr :winning_sign
30
+ attr_accessor :game_status
31
+
32
+ def initialize
33
+ @sign_state_machine = {nil => "X", "X" => "O", "O" => "X"}
34
+ build_grid
35
+ @winning_sign = Cell::EMPTY
36
+ @game_status = IN_PROGRESS
37
+ end
38
+
39
+ #row and column numbers are 1-based
40
+ def mark(row, column)
41
+ self[row, column].mark(current_sign)
42
+ game_over? #updates winning sign
43
+ end
44
+
45
+ def current_sign
46
+ @current_sign = @sign_state_machine[@current_sign]
47
+ end
48
+
49
+ def [](row, column)
50
+ @grid[row-1][column-1]
51
+ end
52
+
53
+ def game_over?
54
+ win? or draw?
55
+ end
56
+
57
+ def win?
58
+ win = (row_win? or column_win? or diagonal_win?)
59
+ self.game_status=WIN if win
60
+ win
61
+ end
62
+
63
+ def reset!
64
+ (1..3).each do |row|
65
+ (1..3).each do |column|
66
+ self[row, column].reset!
67
+ end
68
+ end
69
+ @winning_sign = Cell::EMPTY
70
+ @current_sign = nil
71
+ self.game_status=IN_PROGRESS
72
+ end
73
+
74
+ private
75
+
76
+ def build_grid
77
+ @grid = []
78
+ 3.times do |row_index| #0-based
79
+ @grid << []
80
+ 3.times { @grid[row_index] << Cell.new }
81
+ end
82
+ end
83
+
84
+ def row_win?
85
+ (1..3).each do |row|
86
+ if row_has_same_sign(row)
87
+ @winning_sign = self[row, 1].sign
88
+ return true
89
+ end
90
+ end
91
+ false
92
+ end
93
+
94
+ def column_win?
95
+ (1..3).each do |column|
96
+ if column_has_same_sign(column)
97
+ @winning_sign = self[1, column].sign
98
+ return true
99
+ end
100
+ end
101
+ false
102
+ end
103
+
104
+ #needs refactoring if we ever decide to make the board size dynamic
105
+ def diagonal_win?
106
+ if (self[1, 1].sign == self[2, 2].sign) and (self[2, 2].sign == self[3, 3].sign) and self[1, 1].marked
107
+ @winning_sign = self[1, 1].sign
108
+ return true
109
+ end
110
+ if (self[3, 1].sign == self[2, 2].sign) and (self[2, 2].sign == self[1, 3].sign) and self[3, 1].marked
111
+ @winning_sign = self[3, 1].sign
112
+ return true
113
+ end
114
+ false
115
+ end
116
+
117
+ def draw?
118
+ @board_full = true
119
+ 3.times do |x|
120
+ 3.times do |y|
121
+ @board_full = false if self[x, y].empty
122
+ end
123
+ end
124
+ self.game_status = DRAW if @board_full
125
+ @board_full
126
+ end
127
+
128
+ def row_has_same_sign(number)
129
+ row_sign = self[number, 1].sign
130
+ [2, 3].each do |column|
131
+ return false unless row_sign == (self[number, column].sign)
132
+ end
133
+ true if self[number, 1].marked
134
+ end
135
+
136
+ def column_has_same_sign(number)
137
+ column_sign = self[1, number].sign
138
+ [2, 3].each do |row|
139
+ return false unless column_sign == (self[row, number].sign)
140
+ end
141
+ true if self[1, number].marked
142
+ end
143
+
144
+ end
145
+ end
@@ -0,0 +1,48 @@
1
+ # Copyright (c) 2007-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ class TicTacToe
23
+ class Cell
24
+ EMPTY = ""
25
+ attr_accessor :sign, :empty
26
+
27
+ def initialize
28
+ reset!
29
+ end
30
+
31
+ def mark(sign)
32
+ self.sign = sign
33
+ end
34
+
35
+ def reset!
36
+ self.sign = EMPTY
37
+ end
38
+
39
+ def sign=(sign_symbol)
40
+ @sign = sign_symbol
41
+ self.empty = sign == EMPTY
42
+ end
43
+
44
+ def marked
45
+ !empty
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,85 @@
1
+ require 'glimmer-dsl-libui'
2
+
3
+ require_relative "tic_tac_toe/board"
4
+
5
+ class TicTacToe
6
+ include Glimmer
7
+
8
+ def initialize
9
+ @tic_tac_toe_board = Board.new
10
+ end
11
+
12
+ def launch
13
+ create_gui
14
+ register_observers
15
+ @main_window.show
16
+ end
17
+
18
+ def register_observers
19
+ Glimmer::DataBinding::Observer.proc do |game_status|
20
+ display_win_message if game_status == Board::WIN
21
+ display_draw_message if game_status == Board::DRAW
22
+ end.observe(@tic_tac_toe_board, :game_status)
23
+
24
+ 3.times.map do |row|
25
+ 3.times.map do |column|
26
+ Glimmer::DataBinding::Observer.proc do |sign|
27
+ @cells[row][column].string = sign
28
+ end.observe(@tic_tac_toe_board[row + 1, column + 1], :sign) # board model is 1-based
29
+ end
30
+ end
31
+ end
32
+
33
+ def create_gui
34
+ @main_window = window('Tic-Tac-Toe', 180, 180) {
35
+ resizable false
36
+
37
+ @cells = []
38
+ vertical_box {
39
+ padded false
40
+
41
+ 3.times.map do |row|
42
+ @cells << []
43
+ horizontal_box {
44
+ padded false
45
+
46
+ 3.times.map do |column|
47
+ area {
48
+ path {
49
+ square(0, 0, 60)
50
+
51
+ stroke :black, thickness: 2
52
+ }
53
+ text(23, 19) {
54
+ @cells[row] << string('') {
55
+ font family: 'Arial', size: 20
56
+ }
57
+ }
58
+ on_mouse_up do
59
+ @tic_tac_toe_board.mark(row + 1, column + 1) # board model is 1-based
60
+ end
61
+ }
62
+ end
63
+ }
64
+ end
65
+ }
66
+ }
67
+ end
68
+
69
+ def display_win_message
70
+ display_game_over_message("Player #{@tic_tac_toe_board.winning_sign} has won!")
71
+ end
72
+
73
+ def display_draw_message
74
+ display_game_over_message("Draw!")
75
+ end
76
+
77
+ def display_game_over_message(message_text)
78
+ Glimmer::LibUI.queue_main do
79
+ msg_box('Game Over', message_text)
80
+ @tic_tac_toe_board.reset!
81
+ end
82
+ end
83
+ end
84
+
85
+ TicTacToe.new.launch
Binary file
@@ -78,6 +78,7 @@ module Glimmer
78
78
  when 'on_destroy'
79
79
  on_destroy(&listener)
80
80
  else
81
+ default_behavior_listener = nil
81
82
  if listener_name == 'on_closing'
82
83
  default_behavior_listener = Proc.new do
83
84
  return_value = listener.call(self)
@@ -90,9 +91,45 @@ module Glimmer
90
91
  end
91
92
  end
92
93
  end
93
- super(listener_name, &default_behavior_listener)
94
+ super(listener_name, &(default_behavior_listener || listener))
94
95
  end
95
96
  end
97
+
98
+ def content_size(*args)
99
+ if args.empty?
100
+ width = Fiddle::Pointer.malloc(8)
101
+ height = Fiddle::Pointer.malloc(8)
102
+ ::LibUI.window_content_size(@libui, width, height)
103
+ width = width[0, 8].unpack1('i')
104
+ height = height[0, 8].unpack1('i')
105
+ [width, height]
106
+ else
107
+ args = args.first if args.size == 1 && args.first.is_a?(Array)
108
+ super
109
+ @width = args[0]
110
+ @height = args[1]
111
+ end
112
+ end
113
+ alias content_size= content_size
114
+ alias set_content_size content_size
115
+
116
+ def resizable(value = nil)
117
+ if value.nil?
118
+ @resizable = true if @resizable.nil?
119
+ @resizable
120
+ else
121
+ @resizable = value
122
+ if !@resizable && !@resizable_listener_registered
123
+ handle_listener('on_content_size_changed') do
124
+ set_content_size(@width, @height) unless @resizable
125
+ end
126
+ @resizable_listener_registered = true
127
+ end
128
+ end
129
+ end
130
+ alias resizable? resizable
131
+ alias resizable= resizable
132
+ alias set_resizable resizable
96
133
 
97
134
  private
98
135
 
@@ -107,6 +144,8 @@ module Glimmer
107
144
  construction_args[2] = DEFAULT_HEIGHT if construction_args.size == 2
108
145
  construction_args[3] = DEFAULT_HAS_MENUBAR if construction_args.size == 3
109
146
  construction_args[3] = Glimmer::LibUI.boolean_to_integer(construction_args[3]) if construction_args.size == 4 && (construction_args[3].is_a?(TrueClass) || construction_args[3].is_a?(FalseClass))
147
+ @width = construction_args[1]
148
+ @height = construction_args[2]
110
149
  @libui = ControlProxy.new_control(@keyword, construction_args)
111
150
  @libui.tap do
112
151
  handle_listener('on_closing') do