glimmer-dsl-libui 0.9.7 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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