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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +593 -137
- data/VERSION +1 -1
- data/docs/examples/GLIMMER-DSL-LIBUI-ADVANCED-EXAMPLES.md +5 -5
- data/docs/examples/GLIMMER-DSL-LIBUI-BASIC-EXAMPLES.md +23 -0
- data/examples/basic_composite_shape.rb +2 -0
- data/examples/basic_custom_shape.rb +168 -0
- data/glimmer-dsl-libui.gemspec +0 -0
- data/lib/glimmer/dsl/libui/custom_shape_expression.rb +58 -0
- data/lib/glimmer/dsl/libui/dsl.rb +1 -0
- data/lib/glimmer/dsl/libui/listener_expression.rb +6 -1
- data/lib/glimmer/launcher.rb +2 -5
- data/lib/glimmer/libui/custom_control.rb +25 -19
- data/lib/glimmer/libui/custom_shape.rb +258 -0
- data/lib/glimmer/rake_task/scaffold.rb +19 -13
- data/lib/glimmer/rake_task.rb +18 -18
- data/lib/glimmer-dsl-libui.rb +1 -1
- metadata +5 -2
@@ -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
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
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)}
|
data/lib/glimmer/rake_task.rb
CHANGED
@@ -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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
|
data/lib/glimmer-dsl-libui.rb
CHANGED
@@ -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
|
-
|
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.
|
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-
|
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
|