glimmer-dsl-libui 0.5.4 → 0.5.7

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.
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