glimmer-dsl-tk 0.0.25 → 0.0.29

Sign up to get free protection for your applications and to get access to all the features.
@@ -34,7 +34,9 @@ module Glimmer
34
34
  begin
35
35
  class_name = "#{keyword.camelcase(:upper)}Proxy".to_sym
36
36
  Glimmer::Tk.const_get(class_name)
37
- rescue
37
+ rescue => e
38
+ Glimmer::Config.logger.debug {"Unable to instantiate custom class name for #{keyword} ... defaulting to Glimmer::Tk::WidgetProxy"}
39
+ Glimmer::Config.logger.debug {e.full_message}
38
40
  Glimmer::Tk::WidgetProxy
39
41
  end
40
42
  end
@@ -54,13 +56,15 @@ module Glimmer
54
56
  tk_widget_class = eval(tk_widget_name)
55
57
  break
56
58
  rescue RuntimeError, SyntaxError, NameError => e
57
- Glimmer::Config.logger.debug e.full_message
59
+ Glimmer::Config.logger.debug {e.full_message}
58
60
  end
59
61
  end
60
- tk_widget_class
62
+ tk_widget_class if tk_widget_class.respond_to?(:new)
61
63
  end
62
64
  end
63
65
 
66
+ FONTS_PREDEFINED = %w[default text fixed menu heading caption small_caption icon tooltip]
67
+
64
68
  attr_reader :parent_proxy, :tk, :args, :keyword, :children
65
69
 
66
70
  # Initializes a new Tk Widget
@@ -104,6 +108,8 @@ module Glimmer
104
108
  @tk.send(attribute_setter(attribute), @tk.send(attribute))
105
109
  result = true
106
110
  rescue => e
111
+ Glimmer::Config.logger.debug { "No tk attribute setter for #{attribute}" }
112
+ Glimmer::Config.logger.debug { e.full_message }
107
113
  result = false
108
114
  end
109
115
  result
@@ -114,7 +120,8 @@ module Glimmer
114
120
  # TK Widget currently doesn't support respond_to? properly, so I have to resort to this trick for now
115
121
  @tk.send(attribute)
116
122
  true
117
- rescue
123
+ rescue => e
124
+ Glimmer::Config.logger.debug { "No tk attribute getter setter for #{attribute}" }
118
125
  false
119
126
  end
120
127
  end
@@ -125,7 +132,8 @@ module Glimmer
125
132
  begin
126
133
  @tk.tile_instate(attribute)
127
134
  true
128
- rescue
135
+ rescue => e
136
+ Glimmer::Config.logger.debug { "No tk state for #{attribute}" }
129
137
  false
130
138
  end
131
139
  else
@@ -144,45 +152,55 @@ module Glimmer
144
152
  tk_widget_has_attribute_getter_setter?(attribute) or
145
153
  has_state?(attribute) or
146
154
  has_attributes_attribute?(attribute) or
147
- respond_to?(attribute_setter(attribute), args)
155
+ respond_to?(attribute_setter(attribute), args) or
156
+ respond_to?(attribute_setter(attribute), *args, super_only: true) or
157
+ respond_to?(attribute, *args, super_only: true)
148
158
  end
149
159
 
150
160
  def set_attribute(attribute, *args)
151
- widget_custom_attribute = widget_custom_attribute_mapping[tk.class] && widget_custom_attribute_mapping[tk.class][attribute.to_s]
152
- if respond_to?(attribute, super_only: true)
153
- send(attribute, *args)
154
- elsif respond_to?(attribute_setter(attribute), super_only: true)
155
- send(attribute_setter(attribute), *args)
156
- elsif widget_custom_attribute
157
- widget_custom_attribute[:setter][:invoker].call(@tk, args)
158
- elsif tk_widget_has_attribute_setter?(attribute)
159
- unless args.size == 1 && @tk.send(attribute) == args.first
160
- if args.size == 1
161
- @tk.send(attribute_setter(attribute), *args)
161
+ begin
162
+ widget_custom_attribute = widget_custom_attribute_mapping[tk.class] && widget_custom_attribute_mapping[tk.class][attribute.to_s]
163
+ if respond_to?(attribute_setter(attribute), super_only: true)
164
+ send(attribute_setter(attribute), *args)
165
+ elsif respond_to?(attribute, super_only: true) && self.class.instance_method(attribute).parameters.size > 0
166
+ send(attribute, *args)
167
+ elsif widget_custom_attribute
168
+ widget_custom_attribute[:setter][:invoker].call(@tk, args)
169
+ elsif tk_widget_has_attribute_setter?(attribute)
170
+ unless args.size == 1 && @tk.send(attribute) == args.first
171
+ if args.size == 1
172
+ @tk.send(attribute_setter(attribute), *args)
173
+ else
174
+ @tk.send(attribute_setter(attribute), args)
175
+ end
176
+ end
177
+ elsif tk_widget_has_attribute_getter_setter?(attribute)
178
+ @tk.send(attribute, *args)
179
+ elsif has_state?(attribute)
180
+ attribute = attribute.sub(/=$/, '')
181
+ if !!args.first
182
+ @tk.tile_state(attribute)
162
183
  else
163
- @tk.send(attribute_setter(attribute), args)
184
+ @tk.tile_state("!#{attribute}")
164
185
  end
165
- end
166
- elsif tk_widget_has_attribute_getter_setter?(attribute)
167
- @tk.send(attribute, *args)
168
- elsif has_state?(attribute)
169
- attribute = attribute.sub(/=$/, '')
170
- if !!args.first
171
- @tk.tile_state(attribute)
186
+ elsif has_attributes_attribute?(attribute)
187
+ attribute = attribute.sub(/=$/, '')
188
+ @tk.attributes(attribute, args.first)
172
189
  else
173
- @tk.tile_state("!#{attribute}")
190
+ raise "#{self} cannot handle attribute #{attribute} with args #{args.inspect}"
174
191
  end
175
- elsif has_attributes_attribute?(attribute)
176
- attribute = attribute.sub(/=$/, '')
177
- @tk.attributes(attribute, args.first)
178
- else
179
- send(attribute_setter(attribute), args)
192
+ rescue => e
193
+ Glimmer::Config.logger.debug {"Failed to set attribute #{attribute} with args #{args.inspect}. Attempting to set through style instead..."}
194
+ Glimmer::Config.logger.debug {e.full_message}
195
+ apply_style(attribute => args.first)
180
196
  end
181
197
  end
182
198
 
183
199
  def get_attribute(attribute)
184
200
  widget_custom_attribute = widget_custom_attribute_mapping[tk.class] && widget_custom_attribute_mapping[tk.class][attribute.to_s]
185
- if widget_custom_attribute
201
+ if respond_to?(attribute, super_only: true)
202
+ send(attribute)
203
+ elsif widget_custom_attribute
186
204
  widget_custom_attribute[:getter][:invoker].call(@tk, args)
187
205
  elsif tk_widget_has_attribute_getter_setter?(attribute)
188
206
  @tk.send(attribute)
@@ -201,9 +219,17 @@ module Glimmer
201
219
  "#{attribute}="
202
220
  end
203
221
 
222
+ def style=(styles)
223
+ styles.each do |attribute, value|
224
+ apply_style(attribute => value)
225
+ end
226
+ end
227
+
204
228
  def grid(options = {})
205
229
  options = options.stringify_keys
206
230
  index_in_parent = @parent_proxy.children.index(self)
231
+ options['rowspan'] = options.delete('row_span') if options.keys.include?('row_span')
232
+ options['columnspan'] = options.delete('column_span') if options.keys.include?('column_span')
207
233
  options['rowweight'] = options.delete('row_weight') if options.keys.include?('row_weight')
208
234
  options['columnweight'] = options.delete('column_weight') if options.keys.include?('column_weight')
209
235
  options['columnweight'] = options['rowweight'] = options.delete('weight') if options.keys.include?('weight')
@@ -221,6 +247,25 @@ module Glimmer
221
247
  @tk.grid(options)
222
248
  end
223
249
 
250
+ def font=(value)
251
+ if (value.is_a?(Symbol) || value.is_a?(String)) && FONTS_PREDEFINED.include?(value.to_s.downcase)
252
+ @tk.font = "tk_#{value}_font".camelcase(:upper)
253
+ else
254
+ @tk.font = value.is_a?(TkFont) ? value : TkFont.new(value)
255
+ end
256
+ rescue => e
257
+ Glimmer::Config.logger.debug {"Failed to set attribute #{attribute} with args #{args.inspect}. Attempting to set through style instead..."}
258
+ Glimmer::Config.logger.debug {e.full_message}
259
+ apply_style({"font" => value})
260
+ end
261
+
262
+ def apply_style(options)
263
+ @@style_number = 0 unless defined?(@@style_number)
264
+ style = "style#{@@style_number += 1}.#{@tk.class.name.split('::').last}"
265
+ ::Tk::Tile::Style.configure(style, options)
266
+ @tk.style = style
267
+ end
268
+
224
269
  def widget_custom_attribute_mapping
225
270
  # TODO consider extracting to modules/subclasses
226
271
  @widget_custom_attribute_mapping ||= {
@@ -325,12 +370,20 @@ module Glimmer
325
370
  @tk.command {
326
371
  observer.call(@tk.textvariable&.value)
327
372
  }
373
+ @tk.validate('key')
374
+ @tk.validatecommand { |validate_args|
375
+ observer.call(validate_args.value)
376
+ new_icursor = validate_args.index
377
+ new_icursor += validate_args.string.size if validate_args.action == 1
378
+ @tk.icursor = new_icursor
379
+ true
380
+ }
328
381
  end,
329
382
  },
330
383
  ::Tk::Text => {
331
- 'text' => lambda do |observer|
384
+ 'value' => lambda do |observer|
332
385
  handle_listener('modified') do
333
- observer.call(text)
386
+ observer.call(value)
334
387
  end
335
388
  end,
336
389
  },
@@ -371,6 +424,7 @@ module Glimmer
371
424
  begin
372
425
  @tk.bind(listener_name, &listener)
373
426
  rescue => e
427
+ Glimmer::Config.logger.debug {"Unable to bind to #{listener_name} .. attempting to surround with <>"}
374
428
  Glimmer::Config.logger.debug {e.full_message}
375
429
  listener_name = "<#{listener_name}" if !listener_name.start_with?('<')
376
430
  listener_name = "#{listener_name}>" if !listener_name.end_with?('>')
@@ -74,8 +74,8 @@ class MetaSample
74
74
  def launch
75
75
  @root = root {
76
76
  title 'Glimmer Meta-Sample'
77
- width 700
78
- height 500
77
+ width 1280
78
+ height 720
79
79
 
80
80
  frame {
81
81
  grid row: 0, column: 0, column_weight: 0, row_weight: 1
@@ -87,7 +87,7 @@ class MetaSample
87
87
 
88
88
  on('command') do
89
89
  @selected_sample_index = index
90
- @code_text.text = File.read(file_path_for(selected_sample))
90
+ @code_text.value = File.read(file_path_for(selected_sample))
91
91
  end
92
92
  }
93
93
  end
@@ -102,7 +102,7 @@ class MetaSample
102
102
  parent_dir = File.join(Dir.home, '.glimmer-dsl-tk', 'samples', 'hello')
103
103
  FileUtils.mkdir_p(parent_dir)
104
104
  sample_file = File.join(parent_dir, "#{selected_sample.underscore}.rb")
105
- File.write(sample_file, @code_text.text)
105
+ File.write(sample_file, @code_text.value)
106
106
  FileUtils.cp_r(File.expand_path('../../icons', __dir__), File.dirname(File.dirname(parent_dir)))
107
107
  FileUtils.cp_r(File.expand_path('../hello/images', __dir__), parent_dir)
108
108
  sample_namespace_directory = File.expand_path("../hello/#{selected_sample.underscore}", __dir__)
@@ -120,7 +120,7 @@ class MetaSample
120
120
  text 'Reset'
121
121
 
122
122
  on('command') do
123
- @code_text.text = File.read(file_path_for(selected_sample))
123
+ @code_text.value = File.read(file_path_for(selected_sample))
124
124
  end
125
125
  }
126
126
  }
@@ -128,7 +128,7 @@ class MetaSample
128
128
 
129
129
  @code_text = text {
130
130
  grid row: 0, column: 1, column_weight: 1
131
- text File.read(file_path_for(selected_sample))
131
+ value File.read(file_path_for(selected_sample))
132
132
  }
133
133
  }
134
134
  @root.open
@@ -0,0 +1,68 @@
1
+ # Copyright (c) 2020-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 'glimmer-dsl-tk'
23
+
24
+ include Glimmer
25
+
26
+ root { |w|
27
+ title 'Hello, Built-in Dialog!'
28
+ width 400
29
+ height 400
30
+ x 150
31
+ y 150
32
+
33
+ frame {
34
+ %w[get_open_file get_multiple_open_file get_save_file choose_directory choose_color].each do |dialog|
35
+ button {
36
+ text dialog.split('_').map(&:capitalize).join(' ')
37
+
38
+ on('command') do
39
+ result = send(dialog, parent: w)
40
+ @result_label.text = [result].flatten.join("\n")
41
+ end
42
+ }
43
+ end
44
+
45
+ button {
46
+ text 'Choose Font'
47
+
48
+ on('command') do
49
+ choose_font(family: 'Courier New', size: '30', weight: 'bold') do |chosen_font|
50
+ @result_label.text = chosen_font(parent: w)
51
+ end
52
+ end
53
+ }
54
+ }
55
+
56
+ frame {
57
+ grid sticky: 'nsew', padx: 15, pady: 15
58
+
59
+ label {
60
+ grid row: 0, column: 0
61
+ text 'Result:'
62
+ }
63
+
64
+ @result_label = label {
65
+ grid row: 0, column: 1
66
+ }
67
+ }
68
+ }.open
@@ -1,3 +1,4 @@
1
+
1
2
  # Copyright (c) 2020-2021 Andy Maleh
2
3
  #
3
4
  # Permission is hereby granted, free of charge, to any person obtaining
@@ -44,7 +45,7 @@ class HelloCombobox
44
45
  title 'Hello, Combobox!'
45
46
 
46
47
  combobox {
47
- state 'readonly'
48
+ readonly true # this applies to text editing only (item selection still triggers a write to model)
48
49
  text <=> [person, :country]
49
50
  }
50
51
 
@@ -0,0 +1,159 @@
1
+ # Copyright (c) 2020-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 "glimmer-dsl-tk"
23
+ require "glimmer/tk/drag_and_drop_extension"
24
+
25
+ include Glimmer
26
+
27
+ root {
28
+ title "Hello, Drag and Drop!"
29
+ frame {
30
+ padding 5
31
+ labelframe {
32
+ text "Drag sources"
33
+ padding 5
34
+ label {
35
+ text "Entry"
36
+ grid :row => 0, :column => 0
37
+ }
38
+ entry {
39
+ text "Drag entry text"
40
+ width 30
41
+ grid :row => 0, :column => 1, :pady => 5, :sticky => "e"
42
+ on_drag_start { |event|
43
+ event.data = event.source.textvariable&.value
44
+ event.source.configure(:cursor => "hand2")
45
+ TkLabel.new(event.tooltip) {
46
+ text event.data + " "
47
+ bg "yellow"
48
+ bitmap "warning"
49
+ compound "right"
50
+ }.pack
51
+ }
52
+ on_drag_motion { |event|
53
+ if event.drop_accepted
54
+ event.source.configure(:cursor => "hand1")
55
+ else
56
+ event.source.configure(:cursor => "hand2")
57
+ end
58
+ event.tooltip.geometry("+#{event.x_root + 10}+#{event.y_root - 4}")
59
+ }
60
+ }
61
+ label {
62
+ text "Label"
63
+ grid :row => 1, :column => 0
64
+ }
65
+ label {
66
+ text "Drag label text"
67
+ width 30
68
+ grid :row => 1, :column => 1, :pady => 10, :sticky => "e"
69
+ drag_source true
70
+ }
71
+ label {
72
+ text "Combobox"
73
+ grid :row => 2, :column => 0
74
+ }
75
+ combobox {
76
+ text "Spain"
77
+ values %w[USA Canada Mexico Columbia UK Australia Germany Italy Spain]
78
+ width 27
79
+ grid :row => 2, :column => 1, :pady => 5, :sticky => "e"
80
+ on_drag_start { |event|
81
+ event.data = event.source.textvariable&.value
82
+ }
83
+ }
84
+ label {
85
+ text "Button"
86
+ grid :row => 3, :column => 0
87
+ }
88
+ button {
89
+ text "Drag it"
90
+ grid :row => 3, :column => 1, :pady => 5, :sticky => "w"
91
+ drag_source true
92
+ }
93
+ }
94
+
95
+ labelframe {
96
+ text "Drop targets"
97
+ grid :sticky => "nsew", :pady => 15
98
+ padding 5
99
+ label {
100
+ text "Entry"
101
+ grid :row => 0, :column => 0
102
+ }
103
+ entry {
104
+ width 30
105
+ grid :row => 0, :column => 1, :pady => 5, :sticky => "e"
106
+ on_drop { |event|
107
+ event.target.textvariable.value = event.data
108
+ }
109
+ }
110
+ label {
111
+ text "Label"
112
+ grid :row => 1, :column => 0
113
+ }
114
+ label {
115
+ width 30
116
+ grid :row => 1, :column => 1, :pady => 10, :sticky => "e"
117
+ borderwidth 2
118
+ relief "solid"
119
+ on_drop { |event|
120
+ event.target.textvariable.value = event.data
121
+ }
122
+ }
123
+ label {
124
+ text "Combobox"
125
+ grid :row => 2, :column => 0
126
+ }
127
+ combobox {
128
+ width 27
129
+ grid :row => 2, :column => 1, :pady => 5, :sticky => "e"
130
+ on_drop { |event|
131
+ event.target.textvariable.value = event.data
132
+ }
133
+ }
134
+ label {
135
+ text "Button"
136
+ grid :row => 3, :column => 0
137
+ }
138
+ button {
139
+ text "Drop here"
140
+ grid :row => 3, :column => 1, :pady => 5, :sticky => "w"
141
+ on_drop { |event|
142
+ event.target.text = event.data
143
+ }
144
+ }
145
+ label {
146
+ text "Checkbutton"
147
+ grid :row => 4, :column => 0
148
+ }
149
+ checkbutton {
150
+ text "Drop here to destroy a widget\n(except button)"
151
+ grid :row => 4, :column => 1, :pady => 5, :sticky => "w"
152
+ on_drop { |event|
153
+ event.target.text = event.data
154
+ event.source.destroy unless event.source.is_a? Tk::Button
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }.open
@@ -38,7 +38,7 @@ class HelloEntry
38
38
  title 'Hello, Entry!'
39
39
 
40
40
  label {
41
- grid sticky: 'ew', column_weight: 1
41
+ grid sticky: 'ew'
42
42
  text 'default entry'
43
43
  }
44
44
  entry {
@@ -72,7 +72,7 @@ class HelloEntry
72
72
 
73
73
  ## this event kicks in just after the text variable is validated and before it is modified
74
74
  on('invalid') do |validate_args|
75
- @validated_entry_label.text = "#{validate_args.string} is not valid!"
75
+ @validated_entry_label.text = "#{validate_args.value} is not valid!"
76
76
  @validated_entry_label.foreground = 'red'
77
77
  end
78
78
 
@@ -27,8 +27,6 @@ root { |r|
27
27
  title 'Hello, Message Box!'
28
28
 
29
29
  frame {
30
- grid sticky: 'nsew', padx: 15, pady: 15
31
-
32
30
  button {
33
31
  text 'Please Click To Win a Surprise'
34
32
 
@@ -80,8 +78,6 @@ root { |r|
80
78
  }
81
79
 
82
80
  frame {
83
- grid sticky: 'nsew', padx: 15, pady: 15
84
-
85
81
  label {
86
82
  grid row: 0, column: 0
87
83
  text 'Result:'
@@ -0,0 +1,69 @@
1
+ # Copyright (c) 2020-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 'glimmer-dsl-tk'
23
+
24
+ include Glimmer
25
+
26
+ root {
27
+ title 'Hello, Separator!'
28
+ width 200
29
+ height 200
30
+
31
+ label {
32
+ grid row: 0, column: 0, min_width: 100, min_height: 100, column_weight: 0, sticky: 'nsew'
33
+ text 'Label 1'
34
+ anchor 'center'
35
+ }
36
+
37
+ separator {
38
+ grid row: 0, column: 1
39
+ orient 'vertical'
40
+ }
41
+
42
+ label {
43
+ grid row: 0, column: 2, min_width: 100, min_height: 100, sticky: 'nsew'
44
+ text 'Label 2'
45
+ anchor 'center'
46
+ }
47
+
48
+ separator {
49
+ grid row: 1, column: 0, column_span: 3
50
+ orient 'horizontal'
51
+ }
52
+
53
+ label {
54
+ grid row: 2, column: 0, min_width: 100, min_height: 100, sticky: 'nsew'
55
+ text 'Label 3'
56
+ anchor 'center'
57
+ }
58
+
59
+ separator {
60
+ grid row: 2, column: 1
61
+ orient 'vertical'
62
+ }
63
+
64
+ label {
65
+ grid row: 2, column: 2, min_width: 100, min_height: 100, sticky: 'nsew'
66
+ text 'Label 4'
67
+ anchor 'center'
68
+ }
69
+ }.open