glimmer-dsl-libui 0.9.7 → 0.10.1

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.
@@ -0,0 +1,258 @@
1
+ # Copyright (c) 2021-2023 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 CustomShape
32
+ include SuperModule
33
+ include DataBinding::ObservableModel
34
+
35
+ # This module was only created to prevent Glimmer from checking method_missing first
36
+ module GlimmerSupersedable
37
+ def method_missing(method_name, *args, &block)
38
+ # TODO Consider supporting a glimmer error silencing option for methods defined here
39
+ # but fail the glimmer DSL for the right reason to avoid seeing noise in the log output
40
+ if block && can_handle_listener?(method_name)
41
+ handle_listener(method_name, &block)
42
+ elsif @body_root.respond_to?(method_name, true)
43
+ @body_root.send(method_name, *args, &block)
44
+ else
45
+ super
46
+ end
47
+ end
48
+
49
+ def respond_to?(method_name, *args, &block)
50
+ result = false
51
+ result ||= super
52
+ result ||= can_handle_listener?(method_name)
53
+ result ||= @body_root.respond_to?(method_name, *args, &block)
54
+ end
55
+ end
56
+
57
+ super_module_included do |klass|
58
+ # TODO clear memoization of Shape.libui_class_for for a keyword if a custom shape was defined with that keyword
59
+ klass.include(Glimmer)
60
+ klass.include(GlimmerSupersedable) # prevent Glimmer from running method_missing first
61
+ Glimmer::LibUI::CustomShape.add_custom_shape_namespaces_for(klass)
62
+ end
63
+
64
+ class << self
65
+ def for(keyword)
66
+ unless flyweight_custom_shape_classes.keys.include?(keyword)
67
+ begin
68
+ extracted_namespaces = keyword.
69
+ to_s.
70
+ split(/__/).map do |namespace|
71
+ namespace.camelcase(:upper)
72
+ end
73
+ custom_shape_namespaces.each do |base|
74
+ extracted_namespaces.reduce(base) do |result, namespace|
75
+ if !result.constants.include?(namespace)
76
+ namespace = result.constants.detect {|c| c.to_s.upcase == namespace.to_s.upcase } || namespace
77
+ end
78
+ begin
79
+ flyweight_custom_shape_classes[keyword] = constant = result.const_get(namespace)
80
+ return constant if constant.ancestors.include?(Glimmer::LibUI::CustomShape)
81
+ flyweight_custom_shape_classes[keyword] = constant
82
+ rescue => e
83
+ # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
84
+ flyweight_custom_shape_classes[keyword] = result
85
+ end
86
+ end
87
+ end
88
+ raise "#{keyword} has no custom shape class!"
89
+ rescue => e
90
+ Glimmer::Config.logger.debug {e.message}
91
+ Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
92
+ flyweight_custom_shape_classes[keyword] = nil
93
+ end
94
+ end
95
+ flyweight_custom_shape_classes[keyword]
96
+ end
97
+
98
+ # Flyweight Design Pattern memoization cache. Can be cleared if memory is needed.
99
+ def flyweight_custom_shape_classes
100
+ @flyweight_custom_shape_classes ||= {}
101
+ end
102
+
103
+ # Returns keyword to use for this custom shape
104
+ def keyword
105
+ self.name.underscore.gsub('::', '__')
106
+ end
107
+
108
+ # Returns shortcut keyword to use for this custom shape (keyword minus namespace)
109
+ def shortcut_keyword
110
+ self.name.underscore.gsub('::', '__').split('__').last
111
+ end
112
+
113
+ def add_custom_shape_namespaces_for(klass)
114
+ Glimmer::LibUI::CustomShape.namespaces_for_class(klass).drop(1).each do |namespace|
115
+ Glimmer::LibUI::CustomShape.custom_shape_namespaces << namespace
116
+ end
117
+ end
118
+
119
+ def namespaces_for_class(m)
120
+ return [m] if m.name.nil?
121
+ namespace_constants = m.name.split(/::/).map(&:to_sym)
122
+ namespace_constants.reduce([Object]) do |output, namespace_constant|
123
+ output += [output.last.const_get(namespace_constant)]
124
+ end[1..-1].uniq.reverse
125
+ end
126
+
127
+ def custom_shape_namespaces
128
+ @custom_shape_namespaces ||= reset_custom_shape_namespaces
129
+ end
130
+
131
+ def reset_custom_shape_namespaces
132
+ @custom_shape_namespaces = Set[Object, Glimmer::LibUI]
133
+ end
134
+
135
+ # Allows defining convenience option accessors for an array of option names
136
+ # Example: `options :color1, :color2` defines `#color1` and `#color2`
137
+ # where they return the instance values `options[:color1]` and `options[:color2]`
138
+ # respectively.
139
+ # Can be called multiple times to set more options additively.
140
+ # When passed no arguments, it returns list of all option names captured so far
141
+ def options(*new_options)
142
+ new_options = new_options.compact.map(&:to_s).map(&:to_sym)
143
+ if new_options.empty?
144
+ @options ||= {} # maps options to defaults
145
+ else
146
+ new_options = new_options.reduce({}) {|new_options_hash, new_option| new_options_hash.merge(new_option => nil)}
147
+ @options = options.merge(new_options)
148
+ def_option_attr_accessors(new_options)
149
+ end
150
+ end
151
+
152
+ def option(new_option, default: nil)
153
+ new_option = new_option.to_s.to_sym
154
+ new_options = {new_option => default}
155
+ @options = options.merge(new_options)
156
+ def_option_attr_accessors(new_options)
157
+ end
158
+
159
+ def def_option_attr_accessors(new_options)
160
+ new_options.each do |option, default|
161
+ class_eval <<-end_eval, __FILE__, __LINE__
162
+ def #{option}
163
+ options[:#{option}]
164
+ end
165
+
166
+ def #{option}=(option_value)
167
+ self.options[:#{option}] = option_value
168
+ end
169
+ end_eval
170
+ end
171
+ end
172
+
173
+ def before_body(&block)
174
+ @before_body_block = block
175
+ end
176
+
177
+ def body(&block)
178
+ @body_block = block
179
+ end
180
+
181
+ def after_body(&block)
182
+ @after_body_block = block
183
+ end
184
+ end
185
+
186
+ attr_reader :body_root, :parent, :parent_proxy, :args, :keyword, :content, :options
187
+
188
+ def initialize(keyword, parent, args, options, &content)
189
+ @parent_proxy = @parent = parent
190
+ options ||= {}
191
+ @options = self.class.options.merge(options)
192
+ @content = ProcTracker.new(content) if content
193
+ execute_hook('before_body')
194
+ body_block = self.class.instance_variable_get("@body_block")
195
+ raise Glimmer::Error, 'Invalid custom shape for having no body! Please define body block!' if body_block.nil?
196
+ @body_root = instance_exec(&body_block)
197
+ raise Glimmer::Error, 'Invalid custom shape for having an empty body! Please fill body block!' if @body_root.nil?
198
+ execute_hook('after_body')
199
+ # TODO deregister all observer_registrations on destroy of the shape once that listener is supported
200
+ # (on_destroy) unless it is the last window closing, in which case exit faster
201
+ post_add_content if content.nil?
202
+ end
203
+
204
+ # Subclasses may override to perform post initialization work on an added child
205
+ def post_initialize_child(child)
206
+ # No Op by default
207
+ end
208
+
209
+ def post_add_content
210
+ # No Op by default
211
+ end
212
+
213
+ def observer_registrations
214
+ @observer_registrations ||= []
215
+ end
216
+
217
+ def can_handle_listener?(listener)
218
+ body_root&.can_handle_listener?(listener.to_s)
219
+ end
220
+
221
+ def handle_listener(listener, &block)
222
+ body_root.handle_listener(listener.to_s, &block)
223
+ end
224
+
225
+ # This method ensures it has an instance method not coming from Glimmer DSL
226
+ def has_instance_method?(method_name)
227
+ respond_to?(method_name) and
228
+ !@body_root.respond_to_libui?(method_name) and
229
+ (method(method_name) rescue nil) and
230
+ !method(method_name)&.source_location&.first&.include?('glimmer/dsl/engine.rb') and
231
+ !method(method_name)&.source_location&.first&.include?('glimmer/libui/shape.rb')
232
+ end
233
+
234
+ # Returns content block if used as an attribute reader (no args)
235
+ # Otherwise, if a block is passed, it adds it as content to this custom shape
236
+ def content(&block)
237
+ if block_given?
238
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Libui::CustomShapeExpression.new, self.class.keyword, &block)
239
+ else
240
+ @content
241
+ end
242
+ end
243
+
244
+ private
245
+
246
+ def execute_hook(hook_name)
247
+ hook_block = self.class.instance_variable_get("@#{hook_name}_block")
248
+ return if hook_block.nil?
249
+ temp_method_name = "#{hook_name}_block_#{hook_block.hash.abs}_#{(Time.now.to_f * 1_000_000).to_i}"
250
+ singleton_class.define_method(temp_method_name, &hook_block)
251
+ send(temp_method_name)
252
+ singleton_class.send(:remove_method, temp_method_name)
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ Dir[File.expand_path("./#{File.basename(__FILE__, '.rb')}/*.rb", __dir__)].each {|f| require f}
@@ -432,8 +432,6 @@ module Glimmer
432
432
  end
433
433
 
434
434
  class #{class_name(app_name)}
435
- include Glimmer
436
-
437
435
  APP_ROOT = File.expand_path('../..', __FILE__)
438
436
  VERSION = File.read(File.join(APP_ROOT, 'VERSION'))
439
437
  LICENSE = File.read(File.join(APP_ROOT, 'LICENSE.txt'))
@@ -635,7 +633,7 @@ require '#{window_type == :app ? current_dir_name : namespace}/model/greeting'
635
633
  #
636
634
  body {
637
635
  window {
638
- # Replace example content below with custom window content
636
+ # Replace example content below with your own custom window content
639
637
  content_size 240, 240
640
638
  title '#{human_name(namespace)}'
641
639
 
@@ -811,17 +809,24 @@ end
811
809
  ## Add shape content under custom shape body
812
810
  #
813
811
  body {
814
- # Replace example content below with custom shape content
812
+ # Replace example content below (heart shape) with your own custom shape content
815
813
  shape(location_x, location_y) {
816
- path {
817
- background background_color
818
- cubic size_width - size_width*0.66, size_height/2 - size_height*0.33, size_width*0.65 - size_width*0.66, 0 - size_height*0.33, size_width/2 - size_width*0.66, size_height*0.75 - size_height*0.33, size_width - size_width*0.66, size_height - size_height*0.33
819
- }
820
-
821
- path {
822
- background background_color
823
- cubic size_width - size_width*0.66, size_height/2 - size_height*0.33, size_width*1.35 - size_width*0.66, 0 - size_height*0.33, size_width*1.5 - size_width*0.66, size_height*0.75 - size_height*0.33, size_width - size_width*0.66, size_height - size_height*0.33
824
- }
814
+ # This fill color is shared under all direct children of `shape`
815
+ fill background_color
816
+
817
+ bezier(
818
+ size_width - size_width*0.66, size_height/2 - size_height*0.33,
819
+ size_width*0.65 - size_width*0.66, 0 - size_height*0.33,
820
+ size_width/2 - size_width*0.66, size_height*0.75 - size_height*0.33,
821
+ size_width - size_width*0.66, size_height - size_height*0.33
822
+ )
823
+
824
+ bezier(
825
+ size_width - size_width*0.66, size_height/2 - size_height*0.33,
826
+ size_width*1.35 - size_width*0.66, 0 - size_height*0.33,
827
+ size_width*1.5 - size_width*0.66, size_height*0.75 - size_height*0.33,
828
+ size_width - size_width*0.66, size_height - size_height*0.33
829
+ )
825
830
  }
826
831
  }
827
832
 
@@ -835,6 +840,7 @@ end
835
840
  namespace_type = class_name(namespace) == class_name(current_dir_name) ? 'class' : 'module'
836
841
 
837
842
  <<-MULTI_LINE_STRING
843
+ # Delete this example model and replace with your own model
838
844
  #{namespace_type} #{class_name(namespace)}
839
845
  module Model
840
846
  class #{class_name(model_name)}
@@ -114,15 +114,15 @@ namespace :glimmer do
114
114
  task :custom_control, [:name, :namespace] => :customcontrol
115
115
  task :"custom-control", [:name, :namespace] => :customcontrol
116
116
 
117
- # desc 'Scaffold Glimmer::UI::CustomShape subclass (part of a view) under app/views (namespace is optional) [alt: scaffold:cs]'
118
- # task :customshape, [:name, :namespace] do |t, args|
119
- # require_relative 'rake_task/scaffold'
120
- # Glimmer::RakeTask::Scaffold.custom_shape(args[:name], args[:namespace])
121
- # end
122
- #
123
- # task :cs, [:name, :namespace] => :customshape
124
- # task :custom_shape, [:name, :namespace] => :customshape
125
- # task :"custom-shape", [:name, :namespace] => :customshape
117
+ desc 'Scaffold Glimmer::UI::CustomShape subclass (part of a view) under app/views (namespace is optional) [alt: scaffold:cs]'
118
+ task :customshape, [:name, :namespace] do |t, args|
119
+ require_relative 'rake_task/scaffold'
120
+ Glimmer::RakeTask::Scaffold.custom_shape(args[:name], args[:namespace])
121
+ end
122
+
123
+ task :cs, [:name, :namespace] => :customshape
124
+ task :custom_shape, [:name, :namespace] => :customshape
125
+ task :"custom-shape", [:name, :namespace] => :customshape
126
126
 
127
127
  namespace :gem do
128
128
  desc 'Scaffold Glimmer::UI::CustomWindow subclass (full window view) under its own Ruby gem + app project (namespace is required) [alt: scaffold:gem:cw]'
@@ -145,15 +145,15 @@ namespace :glimmer do
145
145
  task :custom_control, [:name, :namespace] => :customcontrol
146
146
  task :"custom-control", [:name, :namespace] => :customcontrol
147
147
 
148
- # desc 'Scaffold Glimmer::UI::CustomShape subclass (part of a view) under its own Ruby gem project (namespace is required) [alt: scaffold:gem:cs]'
149
- # task :customshape, [:name, :namespace] do |t, args|
150
- # require_relative 'rake_task/scaffold'
151
- # Glimmer::RakeTask::Scaffold.custom_shape_gem(args[:name], args[:namespace])
152
- # end
153
- #
154
- # task :cs, [:name, :namespace] => :customshape
155
- # task :custom_shape, [:name, :namespace] => :customshape
156
- # task :"custom-shape", [:name, :namespace] => :customshape
148
+ desc 'Scaffold Glimmer::UI::CustomShape subclass (part of a view) under its own Ruby gem project (namespace is required) [alt: scaffold:gem:cs]'
149
+ task :customshape, [:name, :namespace] do |t, args|
150
+ require_relative 'rake_task/scaffold'
151
+ Glimmer::RakeTask::Scaffold.custom_shape_gem(args[:name], args[:namespace])
152
+ end
153
+
154
+ task :cs, [:name, :namespace] => :customshape
155
+ task :custom_shape, [:name, :namespace] => :customshape
156
+ task :"custom-shape", [:name, :namespace] => :customshape
157
157
  end
158
158
  end
159
159
 
@@ -25,7 +25,7 @@ $LOAD_PATH.unshift(File.expand_path('..', __FILE__))
25
25
  require 'glimmer'
26
26
  require 'perfect-shape'
27
27
  # require 'logging'
28
- # require 'puts_debuggerer' if ENV['pd'].to_s.downcase == 'true'
28
+ require 'puts_debuggerer' if (ENV['PD'] || ENV['pd']).to_s.downcase == 'true'
29
29
  # require 'super_module'
30
30
  require 'color'
31
31
  require 'os'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer-dsl-libui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.7
4
+ version: 0.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-21 00:00:00.000000000 Z
11
+ date: 2023-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glimmer
@@ -389,6 +389,7 @@ files:
389
389
  - examples/basic_child_window.rb
390
390
  - examples/basic_code_area.rb
391
391
  - examples/basic_composite_shape.rb
392
+ - examples/basic_custom_shape.rb
392
393
  - examples/basic_draw_text.rb
393
394
  - examples/basic_draw_text2.rb
394
395
  - examples/basic_entry.rb
@@ -501,6 +502,7 @@ files:
501
502
  - lib/glimmer/dsl/libui/bind_expression.rb
502
503
  - lib/glimmer/dsl/libui/control_expression.rb
503
504
  - lib/glimmer/dsl/libui/custom_control_expression.rb
505
+ - lib/glimmer/dsl/libui/custom_shape_expression.rb
504
506
  - lib/glimmer/dsl/libui/data_binding_expression.rb
505
507
  - lib/glimmer/dsl/libui/dsl.rb
506
508
  - lib/glimmer/dsl/libui/file_expression.rb
@@ -587,6 +589,7 @@ files:
587
589
  - lib/glimmer/libui/custom_control.rb
588
590
  - lib/glimmer/libui/custom_control/code_area.rb
589
591
  - lib/glimmer/libui/custom_control/refined_table.rb
592
+ - lib/glimmer/libui/custom_shape.rb
590
593
  - lib/glimmer/libui/custom_window.rb
591
594
  - lib/glimmer/libui/data_bindable.rb
592
595
  - lib/glimmer/libui/image_path_renderer.rb