glimmer-dsl-libui 0.5.4 → 0.5.7

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.4
1
+ 0.5.7
@@ -0,0 +1,121 @@
1
+ require 'glimmer-dsl-libui'
2
+ require 'facets'
3
+
4
+ Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
5
+
6
+ class FormField
7
+ include Glimmer::LibUI::CustomControl
8
+
9
+ options :model, :attribute
10
+
11
+ body {
12
+ entry { |e|
13
+ label attribute.to_s.underscore.split('_').map(&:capitalize).join(' ')
14
+ text <=> [model, attribute]
15
+ }
16
+ }
17
+ end
18
+
19
+ class AddressForm
20
+ include Glimmer::LibUI::CustomControl
21
+
22
+ options :address
23
+
24
+ body {
25
+ form {
26
+ form_field(model: address, attribute: :street)
27
+ form_field(model: address, attribute: :p_o_box)
28
+ form_field(model: address, attribute: :city)
29
+ form_field(model: address, attribute: :state)
30
+ form_field(model: address, attribute: :zip_code)
31
+ }
32
+ }
33
+ end
34
+
35
+ class LabelPair
36
+ include Glimmer::LibUI::CustomControl
37
+
38
+ options :model, :attribute, :value
39
+
40
+ body {
41
+ horizontal_box {
42
+ label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
43
+ label(value.to_s) {
44
+ text <= [model, attribute]
45
+ }
46
+ }
47
+ }
48
+ end
49
+
50
+ class AddressView
51
+ include Glimmer::LibUI::CustomControl
52
+
53
+ options :address
54
+
55
+ body {
56
+ vertical_box {
57
+ address.each_pair do |attribute, value|
58
+ label_pair(model: address, attribute: attribute, value: value)
59
+ end
60
+ }
61
+ }
62
+ end
63
+
64
+ class ClassBasedCustomControls
65
+ include Glimmer::LibUI::Application # alias: Glimmer::LibUI::CustomWindow
66
+
67
+ before_body do
68
+ @address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
69
+ @address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
70
+ end
71
+
72
+ body {
73
+ window('Class-Based Custom Keyword') {
74
+ margined true
75
+
76
+ horizontal_box {
77
+ vertical_box {
78
+ label('Address 1') {
79
+ stretchy false
80
+ }
81
+
82
+ address_form(address: @address1)
83
+
84
+ horizontal_separator {
85
+ stretchy false
86
+ }
87
+
88
+ label('Address 1 (Saved)') {
89
+ stretchy false
90
+ }
91
+
92
+ address_view(address: @address1)
93
+ }
94
+
95
+ vertical_separator {
96
+ stretchy false
97
+ }
98
+
99
+ vertical_box {
100
+ label('Address 2') {
101
+ stretchy false
102
+ }
103
+
104
+ address_form(address: @address2)
105
+
106
+ horizontal_separator {
107
+ stretchy false
108
+ }
109
+
110
+ label('Address 2 (Saved)') {
111
+ stretchy false
112
+ }
113
+
114
+ address_view(address: @address2)
115
+ }
116
+ }
117
+ }
118
+ }
119
+ end
120
+
121
+ ClassBasedCustomControls.launch
@@ -32,7 +32,7 @@ def label_pair(model, attribute, value)
32
32
  }
33
33
  end
34
34
 
35
- def address(address_model)
35
+ def address_view(address_model)
36
36
  vertical_box {
37
37
  address_model.each_pair do |attribute, value|
38
38
  label_pair(address_model, attribute, value)
@@ -43,7 +43,7 @@ end
43
43
  address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
44
44
  address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
45
45
 
46
- window('Method-Based Custom Keyword') {
46
+ window('Method-Based Custom Controls') {
47
47
  margined true
48
48
 
49
49
  horizontal_box {
@@ -62,7 +62,7 @@ window('Method-Based Custom Keyword') {
62
62
  stretchy false
63
63
  }
64
64
 
65
- address(address1)
65
+ address_view(address1)
66
66
  }
67
67
 
68
68
  vertical_separator {
@@ -84,7 +84,7 @@ window('Method-Based Custom Keyword') {
84
84
  stretchy false
85
85
  }
86
86
 
87
- address(address2)
87
+ address_view(address2)
88
88
  }
89
89
  }
90
90
  }.show
@@ -39,7 +39,7 @@ def label_pair(model, attribute, value)
39
39
  end
40
40
  end
41
41
 
42
- def address(address_model)
42
+ def address_view(address_model)
43
43
  vertical_box {
44
44
  address_model.each_pair do |attribute, value|
45
45
  label_pair(address_model, attribute, value)
@@ -50,7 +50,7 @@ end
50
50
  address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
51
51
  address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
52
52
 
53
- window('Method-Based Custom Keyword') {
53
+ window('Method-Based Custom Controls') {
54
54
  margined true
55
55
 
56
56
  horizontal_box {
@@ -69,7 +69,7 @@ window('Method-Based Custom Keyword') {
69
69
  stretchy false
70
70
  }
71
71
 
72
- address(address1)
72
+ address_view(address1)
73
73
  }
74
74
 
75
75
  vertical_separator {
@@ -91,7 +91,7 @@ window('Method-Based Custom Keyword') {
91
91
  stretchy false
92
92
  }
93
93
 
94
- address(address2)
94
+ address_view(address2)
95
95
  }
96
96
  }
97
97
  }.show
Binary file
@@ -0,0 +1,59 @@
1
+ # Copyright (c) 2021-2022 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'
23
+ require 'glimmer/dsl/expression'
24
+ require 'glimmer/dsl/parent_expression'
25
+ require 'glimmer/dsl/top_level_expression'
26
+ require 'glimmer/libui/custom_control'
27
+ require 'glimmer/libui/custom_window'
28
+
29
+ module Glimmer
30
+ module DSL
31
+ module Libui
32
+ class CustomControlExpression < Expression
33
+ # TODO Consider making custom controls automatically generate static expressions
34
+ include ParentExpression
35
+ include TopLevelExpression
36
+
37
+ def can_interpret?(parent, keyword, *args, &block)
38
+ LibUI::CustomControl.for(keyword)
39
+ end
40
+
41
+ def interpret(parent, keyword, *args, &block)
42
+ options = args.last.is_a?(Hash) ? args.pop : {}
43
+ LibUI::CustomControl.for(keyword).new(keyword, parent, args, options, &block)
44
+ end
45
+
46
+ def add_content(custom_control, keyword, *args, &block)
47
+ options = args.last.is_a?(Hash) ? args.last : {post_add_content: true}
48
+ # TODO consider avoiding source_location
49
+ if block.source_location == custom_control.content&.__getobj__&.source_location
50
+ custom_control.content.call(custom_control) unless custom_control.content.called?
51
+ else
52
+ super
53
+ end
54
+ custom_control.post_add_content if options[:post_add_content]
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -43,6 +43,7 @@ module Glimmer
43
43
  string
44
44
  operation
45
45
  control
46
+ custom_control
46
47
  shape
47
48
  ]
48
49
  )
@@ -32,7 +32,8 @@ module Glimmer
32
32
  (
33
33
  parent.is_a?(Glimmer::LibUI::ControlProxy) or
34
34
  parent.is_a?(Glimmer::LibUI::Shape) or
35
- parent.is_a?(Glimmer::LibUI::AttributedString)
35
+ parent.is_a?(Glimmer::LibUI::AttributedString) or
36
+ parent.is_a?(Glimmer::LibUI::CustomControl)
36
37
  ) and
37
38
  block.nil? and
38
39
  parent.respond_to?("#{keyword}=", *args)
@@ -0,0 +1,252 @@
1
+ # Copyright (c) 2021-2022 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 'super_module'
23
+ require 'glimmer'
24
+ require 'glimmer/error'
25
+ require 'glimmer/proc_tracker'
26
+ require 'glimmer/data_binding/observer'
27
+ require 'glimmer/data_binding/observable_model'
28
+
29
+ module Glimmer
30
+ module LibUI
31
+ module CustomControl
32
+ include SuperModule
33
+ include DataBinding::ObservableModel
34
+
35
+ super_module_included do |klass|
36
+ # TODO clear memoization of WidgetProxy.libui_class_for for a keyword if a custom control was defined with that keyword
37
+ unless klass.name.include?('Glimmer::LibUI::CustomWindow')
38
+ klass.include(Glimmer)
39
+ Glimmer::LibUI::CustomControl.add_custom_control_namespaces_for(klass)
40
+ end
41
+ end
42
+
43
+ class << self
44
+ def for(keyword)
45
+ unless flyweight_custom_control_classes.keys.include?(keyword)
46
+ begin
47
+ extracted_namespaces = keyword.
48
+ to_s.
49
+ split(/__/).map do |namespace|
50
+ namespace.camelcase(:upper)
51
+ end
52
+ custom_control_namespaces.each do |base|
53
+ extracted_namespaces.reduce(base) do |result, namespace|
54
+ if !result.constants.include?(namespace)
55
+ namespace = result.constants.detect {|c| c.to_s.upcase == namespace.to_s.upcase } || namespace
56
+ end
57
+ begin
58
+ flyweight_custom_control_classes[keyword] = constant = result.const_get(namespace)
59
+ return constant if constant.ancestors.include?(Glimmer::LibUI::CustomControl)
60
+ flyweight_custom_control_classes[keyword] = constant
61
+ rescue => e
62
+ # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
63
+ flyweight_custom_control_classes[keyword] = result
64
+ end
65
+ end
66
+ end
67
+ raise "#{keyword} has no custom control class!"
68
+ rescue => e
69
+ Glimmer::Config.logger.debug {e.message}
70
+ Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
71
+ flyweight_custom_control_classes[keyword] = nil
72
+ end
73
+ end
74
+ flyweight_custom_control_classes[keyword]
75
+ end
76
+
77
+ # Flyweight Design Pattern memoization cache. Can be cleared if memory is needed.
78
+ def flyweight_custom_control_classes
79
+ @flyweight_custom_control_classes ||= {}
80
+ end
81
+
82
+ # Returns keyword to use for this custom control
83
+ def keyword
84
+ self.name.underscore.gsub('::', '__')
85
+ end
86
+
87
+ # Returns shortcut keyword to use for this custom control (keyword minus namespace)
88
+ def shortcut_keyword
89
+ self.name.underscore.gsub('::', '__').split('__').last
90
+ end
91
+
92
+ def add_custom_control_namespaces_for(klass)
93
+ Glimmer::LibUI::CustomControl.namespaces_for_class(klass).drop(1).each do |namespace|
94
+ Glimmer::LibUI::CustomControl.custom_control_namespaces << namespace
95
+ end
96
+ end
97
+
98
+ def namespaces_for_class(m)
99
+ return [m] if m.name.nil?
100
+ namespace_constants = m.name.split(/::/).map(&:to_sym)
101
+ namespace_constants.reduce([Object]) do |output, namespace_constant|
102
+ output += [output.last.const_get(namespace_constant)]
103
+ end[1..-1].uniq.reverse
104
+ end
105
+
106
+ def custom_control_namespaces
107
+ @custom_control_namespaces ||= reset_custom_control_namespaces
108
+ end
109
+
110
+ def reset_custom_control_namespaces
111
+ @custom_control_namespaces = Set[Object, Glimmer::LibUI]
112
+ end
113
+
114
+ # Allows defining convenience option accessors for an array of option names
115
+ # Example: `options :color1, :color2` defines `#color1` and `#color2`
116
+ # where they return the instance values `options[:color1]` and `options[:color2]`
117
+ # respectively.
118
+ # Can be called multiple times to set more options additively.
119
+ # When passed no arguments, it returns list of all option names captured so far
120
+ def options(*new_options)
121
+ new_options = new_options.compact.map(&:to_s).map(&:to_sym)
122
+ if new_options.empty?
123
+ @options ||= {} # maps options to defaults
124
+ else
125
+ new_options = new_options.reduce({}) {|new_options_hash, new_option| new_options_hash.merge(new_option => nil)}
126
+ @options = options.merge(new_options)
127
+ def_option_attr_accessors(new_options)
128
+ end
129
+ end
130
+
131
+ def option(new_option, default: nil)
132
+ new_option = new_option.to_s.to_sym
133
+ new_options = {new_option => default}
134
+ @options = options.merge(new_options)
135
+ def_option_attr_accessors(new_options)
136
+ end
137
+
138
+ def def_option_attr_accessors(new_options)
139
+ new_options.each do |option, default|
140
+ class_eval <<-end_eval, __FILE__, __LINE__
141
+ def #{option}
142
+ options[:#{option}]
143
+ end
144
+
145
+ def #{option}=(option_value)
146
+ self.options[:#{option}] = option_value
147
+ end
148
+ end_eval
149
+ end
150
+ end
151
+
152
+ def before_body(&block)
153
+ @before_body_block = block
154
+ end
155
+
156
+ def body(&block)
157
+ @body_block = block
158
+ end
159
+
160
+ def after_body(&block)
161
+ @after_body_block = block
162
+ end
163
+ end
164
+
165
+ attr_reader :body_root, :libui, :parent, :parent_proxy, :args, :keyword, :content, :options
166
+
167
+ def initialize(keyword, parent, args, options, &content)
168
+ @parent_proxy = @parent = parent
169
+ options ||= {}
170
+ @options = self.class.options.merge(options)
171
+ @content = ProcTracker.new(content) if content
172
+ execute_hook('before_body')
173
+ body_block = self.class.instance_variable_get("@body_block")
174
+ raise Glimmer::Error, 'Invalid custom control for having no body! Please define body block!' if body_block.nil?
175
+ @body_root = instance_exec(&body_block)
176
+ raise Glimmer::Error, 'Invalid custom control for having an empty body! Please fill body block!' if @body_root.nil?
177
+ @libui = @body_root.libui
178
+ execute_hook('after_body')
179
+ # TODO deregister all observer_registrations on destroy of the control once that listener is supported
180
+ # (on_destroy) unless it is the last window closing, in which case exit faster
181
+ post_add_content if content.nil?
182
+ end
183
+
184
+ # Subclasses may override to perform post initialization work on an added child
185
+ def post_initialize_child(child)
186
+ # No Op by default
187
+ end
188
+
189
+ def post_add_content
190
+ # No Op by default
191
+ end
192
+
193
+ def observer_registrations
194
+ @observer_registrations ||= []
195
+ end
196
+
197
+ def can_handle_listener?(listener)
198
+ body_root&.can_handle_listener?(listener.to_s)
199
+ end
200
+
201
+ def handle_listener(listener, &block)
202
+ body_root.handle_listener(listener.to_s, &block)
203
+ end
204
+
205
+ # This method ensures it has an instance method not coming from Glimmer DSL
206
+ def has_instance_method?(method_name)
207
+ respond_to?(method_name) and
208
+ !@body_root.respond_to_libui?(method_name) and
209
+ (method(method_name) rescue nil) and
210
+ !method(method_name)&.source_location&.first&.include?('glimmer/dsl/engine.rb') and
211
+ !method(method_name)&.source_location&.first&.include?('glimmer/libui/control_proxy.rb')
212
+ end
213
+
214
+ # Returns content block if used as an attribute reader (no args)
215
+ # Otherwise, if a block is passed, it adds it as content to this custom control
216
+ def content(&block)
217
+ if block_given?
218
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Libui::CustomControlExpression.new, self.class.keyword, &block)
219
+ else
220
+ @content
221
+ end
222
+ end
223
+
224
+ def method_missing(method_name, *args, &block)
225
+ # TODO Consider supporting a glimmer error silencing option for methods defined here
226
+ # but fail the glimmer DSL for the right reason to avoid seeing noise in the log output
227
+ if block && can_handle_listener?(method_name)
228
+ handle_listener(method_name, &block)
229
+ else
230
+ @body_root.send(method_name, *args, &block)
231
+ end
232
+ end
233
+
234
+ def respond_to?(method_name, *args, &block)
235
+ super or
236
+ can_handle_listener?(method_name) or
237
+ @body_root.respond_to?(method_name, *args, &block)
238
+ end
239
+
240
+ private
241
+
242
+ def execute_hook(hook_name)
243
+ hook_block = self.class.instance_variable_get("@#{hook_name}_block")
244
+ return if hook_block.nil?
245
+ temp_method_name = "#{hook_name}_block_#{hook_block.hash.abs}_#{(Time.now.to_f * 1_000_000).to_i}"
246
+ singleton_class.define_method(temp_method_name, &hook_block)
247
+ send(temp_method_name)
248
+ singleton_class.send(:remove_method, temp_method_name)
249
+ end
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,61 @@
1
+ # Copyright (c) 2021-2022 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 'super_module'
23
+ require 'glimmer/libui/custom_control'
24
+ require 'glimmer/error'
25
+
26
+ module Glimmer
27
+ module LibUI
28
+ module CustomWindow
29
+ include SuperModule
30
+ include Glimmer::LibUI::CustomControl
31
+
32
+ class << self
33
+ def launch(*args, &content)
34
+ launched_custom_shell = send(keyword, *args, &content)
35
+ launched_custom_shell.show
36
+ end
37
+ end
38
+
39
+ def initialize(parent, *swt_constants, options, &content)
40
+ super
41
+ raise Glimmer::Error, 'Invalid custom window body root! Must be a window, another custom window, or a custom control with window as its body root!' unless body_root.is_a?(Glimmer::LibUI::ControlProxy::WindowProxy) || body_root.is_a?(Glimmer::LibUI::CustomWindow) || (body_root.is_a?(Glimmer::LibUI::CustomControl) && body_root.body_root.is_a?(Glimmer::LibUI::ControlProxy::WindowProxy))
42
+ end
43
+
44
+ # Classes may override
45
+ def show
46
+ body_root.show
47
+ end
48
+
49
+ # TODO consider using Forwardable instead
50
+ def destroy
51
+ body_root.destroy
52
+ end
53
+
54
+ def destroying?
55
+ body_root.destroying?
56
+ end
57
+ end
58
+
59
+ Application = CustomWindow
60
+ end
61
+ end
@@ -0,0 +1,39 @@
1
+ # Copyright (c) 2007-2022 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 'delegate'
23
+
24
+ module Glimmer
25
+ class ProcTracker < DelegateClass(Proc)
26
+ def initialize(proc)
27
+ super(proc)
28
+ end
29
+
30
+ def call(*args)
31
+ __getobj__.call(*args)
32
+ @called = true
33
+ end
34
+
35
+ def called?
36
+ !!@called
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ module Glimmer
2
+ class << self
3
+ def included(klass)
4
+ klass.extend(Glimmer)
5
+ end
6
+ end
7
+ end
@@ -37,7 +37,7 @@ require 'libui'
37
37
 
38
38
  # Internal requires
39
39
  # require 'ext/glimmer/config'
40
- # require 'ext/glimmer'
40
+ require 'glimmer-dsl-libui/ext/glimmer'
41
41
  require 'glimmer/dsl/libui/dsl'
42
42
  require 'glimmer/libui'
43
43
  Glimmer::Config.loop_max_count = -1