lacci 0.2.2 → 0.3.0
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/Gemfile +2 -0
- data/Gemfile.lock +8 -1
- data/lib/lacci/scarpe_cli.rb +2 -1
- data/lib/lacci/scarpe_core.rb +2 -1
- data/lib/lacci/version.rb +1 -1
- data/lib/scarpe/niente/app.rb +23 -0
- data/lib/scarpe/niente/display_service.rb +62 -0
- data/lib/scarpe/niente/drawable.rb +57 -0
- data/lib/scarpe/niente/logger.rb +29 -0
- data/lib/scarpe/niente/shoes_spec.rb +87 -0
- data/lib/scarpe/niente.rb +20 -0
- data/lib/shoes/app.rb +88 -43
- data/lib/shoes/background.rb +2 -2
- data/lib/shoes/border.rb +2 -2
- data/lib/shoes/builtins.rb +63 -0
- data/lib/shoes/changelog.rb +52 -0
- data/lib/shoes/colors.rb +3 -1
- data/lib/shoes/constants.rb +19 -1
- data/lib/shoes/display_service.rb +39 -16
- data/lib/shoes/download.rb +2 -2
- data/lib/shoes/drawable.rb +380 -0
- data/lib/shoes/drawables/arc.rb +49 -0
- data/lib/shoes/drawables/arrow.rb +41 -0
- data/lib/shoes/drawables/button.rb +73 -0
- data/lib/shoes/{widgets → drawables}/check.rb +5 -4
- data/lib/shoes/{widgets → drawables}/document_root.rb +3 -3
- data/lib/shoes/{widgets → drawables}/edit_box.rb +6 -6
- data/lib/shoes/{widgets → drawables}/edit_line.rb +6 -6
- data/lib/shoes/{widgets → drawables}/flow.rb +6 -6
- data/lib/shoes/{widgets → drawables}/image.rb +6 -6
- data/lib/shoes/{widgets → drawables}/line.rb +7 -5
- data/lib/shoes/drawables/link.rb +34 -0
- data/lib/shoes/drawables/list_box.rb +56 -0
- data/lib/shoes/drawables/para.rb +118 -0
- data/lib/shoes/drawables/progress.rb +14 -0
- data/lib/shoes/drawables/radio.rb +33 -0
- data/lib/shoes/drawables/rect.rb +17 -0
- data/lib/shoes/{widgets → drawables}/shape.rb +6 -7
- data/lib/shoes/{widgets → drawables}/slot.rb +32 -20
- data/lib/shoes/{widgets → drawables}/span.rb +8 -7
- data/lib/shoes/{widgets → drawables}/stack.rb +6 -4
- data/lib/shoes/drawables/star.rb +50 -0
- data/lib/shoes/drawables/subscription_item.rb +93 -0
- data/lib/shoes/drawables/text_drawable.rb +63 -0
- data/lib/shoes/drawables/video.rb +16 -0
- data/lib/shoes/drawables/widget.rb +69 -0
- data/lib/shoes/drawables.rb +31 -0
- data/lib/shoes/errors.rb +28 -0
- data/lib/shoes/log.rb +2 -2
- data/lib/shoes/ruby_extensions.rb +15 -0
- data/lib/shoes/spacing.rb +2 -2
- data/lib/shoes-spec.rb +93 -0
- data/lib/shoes.rb +27 -7
- metadata +55 -28
- data/lib/shoes/widget.rb +0 -218
- data/lib/shoes/widgets/alert.rb +0 -19
- data/lib/shoes/widgets/arc.rb +0 -51
- data/lib/shoes/widgets/button.rb +0 -35
- data/lib/shoes/widgets/font.rb +0 -14
- data/lib/shoes/widgets/link.rb +0 -25
- data/lib/shoes/widgets/list_box.rb +0 -25
- data/lib/shoes/widgets/para.rb +0 -68
- data/lib/shoes/widgets/radio.rb +0 -35
- data/lib/shoes/widgets/star.rb +0 -44
- data/lib/shoes/widgets/subscription_item.rb +0 -60
- data/lib/shoes/widgets/text_widget.rb +0 -51
- data/lib/shoes/widgets/video.rb +0 -15
- data/lib/shoes/widgets.rb +0 -29
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Shoes has a number of built-in methods that are intended to be available everywhere,
|
4
|
+
# in every Shoes and non-Shoes class, for every Shoes app.
|
5
|
+
module Shoes::Builtins
|
6
|
+
# Register the given font with Shoes so that text that wants it can use it.
|
7
|
+
# Also add it to the FONTS constant.
|
8
|
+
#
|
9
|
+
# @param font_file_or_url [String] the filename or URL for the font
|
10
|
+
# @return [void]
|
11
|
+
def font(font_file_or_url)
|
12
|
+
shoes_builtin("font", font_file_or_url)
|
13
|
+
|
14
|
+
font_name = File.basename(font_file_or_url, ".*")
|
15
|
+
Shoes::FONTS << font_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def ask(message_string)
|
19
|
+
shoes_builtin("ask", message_string)
|
20
|
+
end
|
21
|
+
|
22
|
+
def alert(message)
|
23
|
+
shoes_builtin("alert", message)
|
24
|
+
end
|
25
|
+
|
26
|
+
def ask_color(title_bar)
|
27
|
+
shoes_builtin("ask_color", title_bar)
|
28
|
+
end
|
29
|
+
|
30
|
+
def ask_open_file()
|
31
|
+
shoes_builtin("ask_open_file")
|
32
|
+
end
|
33
|
+
|
34
|
+
def ask_save_file()
|
35
|
+
shoes_builtin("ask_save_file")
|
36
|
+
end
|
37
|
+
|
38
|
+
def ask_open_folder()
|
39
|
+
shoes_builtin("ask_open_folder")
|
40
|
+
end
|
41
|
+
|
42
|
+
def ask_save_folder()
|
43
|
+
shoes_builtin("ask_save_folder")
|
44
|
+
end
|
45
|
+
|
46
|
+
def confirm(question)
|
47
|
+
shoes_builtin("confirm", question)
|
48
|
+
end
|
49
|
+
|
50
|
+
# TO ADD: debug, error, info, warn
|
51
|
+
# TO VERIFY OR ADD: gradient, gray, rgb
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def shoes_builtin(cmd_name, *args)
|
56
|
+
Shoes::DisplayService.dispatch_event("builtin", nil, cmd_name, args)
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module Kernel
|
62
|
+
include Shoes::Builtins
|
63
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "log"
|
4
|
+
# require "scarpe/components/modular_logger"
|
5
|
+
|
6
|
+
class Shoes
|
7
|
+
class Changelog
|
8
|
+
# include Shoes::Log
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
#TODO : refer to https://github.com/scarpe-team/scarpe/pull/400
|
12
|
+
# and figure out how to use scarpe logger here without getting duplicate or nil error
|
13
|
+
# Shoes::Log.instance = Scarpe::Components::ModularLogImpl.new
|
14
|
+
# log_init("Changelog")
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_latest_release_info
|
18
|
+
root_dir = File.dirname(__FILE__, 4) # this duplicates constants.rb, but how to share?
|
19
|
+
|
20
|
+
git_dir = "#{root_dir}/.git"
|
21
|
+
revision = nil
|
22
|
+
if File.exist?(git_dir)
|
23
|
+
revision = `git rev-parse HEAD`.chomp
|
24
|
+
end
|
25
|
+
|
26
|
+
changelog_file = "#{root_dir}/CHANGELOG.md"
|
27
|
+
if File.exist?(changelog_file)
|
28
|
+
changelog_content = File.read(changelog_file)
|
29
|
+
release_name_pattern = /^## \[(\d+\.\d+\.\d+)\] - (\d{4}-\d{2}-\d{2}) - (\w+)$/m
|
30
|
+
release_matches = changelog_content.scan(release_name_pattern)
|
31
|
+
latest_release = release_matches.max_by { |version, _date, _name| Gem::Version.new(version) }
|
32
|
+
|
33
|
+
if latest_release
|
34
|
+
#puts "Found release #{latest_release[0]} in CHANGELOG.md"
|
35
|
+
# @log.debug("Found release #{latest_release[0]} in CHANGELOG.md") # Logger isn't initialized yet
|
36
|
+
version_parts = latest_release[0].split(".").map(&:to_i)
|
37
|
+
rel_id = ("%02d%02d%02d" % version_parts).to_i
|
38
|
+
|
39
|
+
return({
|
40
|
+
RELEASE_NAME: latest_release[2],
|
41
|
+
RELEASE_BUILD_DATE: latest_release[1],
|
42
|
+
RELEASE_ID: rel_id,
|
43
|
+
REVISION: revision,
|
44
|
+
})
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
puts "No release found in CHANGELOG.md"
|
49
|
+
{ RELEASE_NAME: nil, RELEASE_BUILD_DATE: nil, RELEASE_ID: nil, REVISION: revision }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/shoes/colors.rb
CHANGED
data/lib/shoes/constants.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative "changelog"
|
4
|
+
class Shoes
|
4
5
|
module Constants
|
5
6
|
def self.find_lib_dir
|
6
7
|
begin
|
@@ -14,16 +15,33 @@ module Shoes
|
|
14
15
|
[ENV["HOME"], ".shoes"],
|
15
16
|
[Dir.tmpdir, "shoes"],
|
16
17
|
]
|
18
|
+
|
17
19
|
top, file = homes.detect { |home_top, _| home_top && File.exist?(home_top) }
|
18
20
|
File.join(top, file)
|
19
21
|
end
|
20
22
|
|
23
|
+
# A temp dir for Shoes app files
|
21
24
|
LIB_DIR = find_lib_dir
|
22
25
|
|
26
|
+
# the Shoes library dir
|
27
|
+
DIR = File.dirname(__FILE__, 4)
|
28
|
+
|
23
29
|
# Math constants from Shoes3
|
24
30
|
RAD2PI = 0.01745329251994329577
|
25
31
|
TWO_PI = 6.28318530717958647693
|
26
32
|
HALF_PI = 1.57079632679489661923
|
27
33
|
PI = 3.14159265358979323846
|
34
|
+
|
35
|
+
# This should be set up by the Display Service when it loads
|
36
|
+
FONTS = []
|
28
37
|
end
|
38
|
+
|
39
|
+
# Access and assign the release constants
|
40
|
+
changelog_instance = Shoes::Changelog.new
|
41
|
+
RELEASE_INFO = changelog_instance.get_latest_release_info
|
42
|
+
RELEASE_NAME = RELEASE_INFO[:RELEASE_NAME]
|
43
|
+
RELEASE_ID = RELEASE_INFO[:RELEASE_ID]
|
44
|
+
RELEASE_BUILD_DATE = RELEASE_INFO[:RELEASE_BUILD_DATE]
|
45
|
+
RELEASE_TYPE = "LOOSE_SHOES" # This isn't really a thing any more
|
46
|
+
REVISION = RELEASE_INFO[:REVISION]
|
29
47
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Lacci Shoes apps operate in multiple layers. A Shoes
|
4
|
-
# plain, simple Ruby objects. And then a display-service
|
3
|
+
# Lacci Shoes apps operate in multiple layers. A Shoes drawable tree exists as fairly
|
4
|
+
# plain, simple Ruby objects. And then a display-service drawable tree integrates with
|
5
5
|
# the display technology. This lets us use Ruby as our API while
|
6
6
|
# not tying it too closely to the limitations of Webview, WASM, LibUI, etc.
|
7
7
|
#
|
@@ -15,18 +15,18 @@
|
|
15
15
|
#
|
16
16
|
# ## Events
|
17
17
|
#
|
18
|
-
# Events are a lot of what tie the Shoes
|
18
|
+
# Events are a lot of what tie the Shoes drawables and the display service together.
|
19
19
|
#
|
20
|
-
# Shoes
|
20
|
+
# Shoes drawables *expect* to operate in a fairly "hands off" mode where they record
|
21
21
|
# to an event queue to send to the display service, and the display service records
|
22
22
|
# events to send back.
|
23
23
|
#
|
24
24
|
# When a Shoes handler takes an action (e.g. some_para.replace(),) the relevant
|
25
25
|
# call will be dispatched as a :display event, to be sent to the display service.
|
26
26
|
# And when a display-side event occurs (e.g. user pushes a button,) it will be
|
27
|
-
# dispatched as a :shoes event, to be sent to the Shoes tree of
|
27
|
+
# dispatched as a :shoes event, to be sent to the Shoes tree of drawables.
|
28
28
|
#
|
29
|
-
|
29
|
+
class Shoes
|
30
30
|
class DisplayService
|
31
31
|
class << self
|
32
32
|
# This is in the eigenclass/metaclass, *not* instances of DisplayService
|
@@ -128,22 +128,32 @@ module Shoes
|
|
128
128
|
|
129
129
|
# These methods are an interface to DisplayService objects.
|
130
130
|
|
131
|
-
def
|
131
|
+
def create_display_drawable_for(drawable_class_name, drawable_id, properties, is_widget:)
|
132
132
|
raise "Override in DisplayService implementation!"
|
133
133
|
end
|
134
134
|
|
135
|
-
def
|
136
|
-
|
137
|
-
|
135
|
+
def set_drawable_pairing(id, display_drawable)
|
136
|
+
if id.nil?
|
137
|
+
raise Shoes::Errors::BadLinkableIdError, "Linkable ID may not be nil!"
|
138
|
+
end
|
139
|
+
|
140
|
+
@display_drawable_for ||= {}
|
141
|
+
if @display_drawable_for[id]
|
142
|
+
raise Shoes::Errors::DuplicateCreateDrawableError, "There is already a drawable for #{id.inspect}! Not setting a new one."
|
143
|
+
end
|
144
|
+
|
145
|
+
@display_drawable_for[id] = display_drawable
|
146
|
+
nil
|
138
147
|
end
|
139
148
|
|
140
|
-
def
|
141
|
-
|
142
|
-
|
143
|
-
|
149
|
+
def query_display_drawable_for(id, nil_ok: false)
|
150
|
+
@display_drawable_for ||= {}
|
151
|
+
display_drawable = @display_drawable_for[id]
|
152
|
+
unless display_drawable || nil_ok
|
153
|
+
raise "Could not find display drawable for linkable ID #{id.inspect}!"
|
144
154
|
end
|
145
155
|
|
146
|
-
|
156
|
+
display_drawable
|
147
157
|
end
|
148
158
|
|
149
159
|
def destroy
|
@@ -163,6 +173,8 @@ module Shoes
|
|
163
173
|
|
164
174
|
def initialize(linkable_id: object_id)
|
165
175
|
@linkable_id = linkable_id
|
176
|
+
@subscriptions = {}
|
177
|
+
@display_drawable_for ||= {}
|
166
178
|
end
|
167
179
|
|
168
180
|
def send_self_event(*args, event_name:, **kwargs)
|
@@ -174,11 +186,22 @@ module Shoes
|
|
174
186
|
end
|
175
187
|
|
176
188
|
def bind_shoes_event(event_name:, target: nil, &handler)
|
177
|
-
DisplayService.subscribe_to_event(event_name, target, &handler)
|
189
|
+
sub = DisplayService.subscribe_to_event(event_name, target, &handler)
|
190
|
+
@subscriptions[sub] = true
|
191
|
+
sub
|
178
192
|
end
|
179
193
|
|
180
194
|
def unsub_shoes_event(unsub_id)
|
195
|
+
unless @subscriptions[unsub_id]
|
196
|
+
$stderr.puts "Unsubscribing from event that isn't in subscriptions! #{unsub_id.inspect}"
|
197
|
+
end
|
181
198
|
DisplayService.unsub_from_events(unsub_id)
|
199
|
+
@subscriptions.delete unsub_id
|
200
|
+
end
|
201
|
+
|
202
|
+
def unsub_all_shoes_events
|
203
|
+
@subscriptions.keys.each { |k| DisplayService.unsub_from_events(k) }
|
204
|
+
@subscriptions.clear
|
182
205
|
end
|
183
206
|
end
|
184
207
|
end
|
data/lib/shoes/download.rb
CHANGED
@@ -0,0 +1,380 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Shoes
|
4
|
+
# Shoes::Drawable
|
5
|
+
#
|
6
|
+
# This is the display-service portable Shoes Drawable interface. Visible Shoes
|
7
|
+
# drawables like buttons inherit from this. Compound drawables made of multiple
|
8
|
+
# different smaller Drawables inherit from it in their various apps or libraries.
|
9
|
+
# The Shoes Drawable helps build a Shoes-side drawable tree, with parents and
|
10
|
+
# children. Any API that applies to all drawables (e.g. remove) should be
|
11
|
+
# defined here.
|
12
|
+
#
|
13
|
+
class Drawable < Shoes::Linkable
|
14
|
+
include Shoes::Log
|
15
|
+
include Shoes::Colors
|
16
|
+
|
17
|
+
# All Drawables have these so they go in Shoes::Drawable and are inherited
|
18
|
+
@shoes_events = ["parent", "destroy", "prop_change"]
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_accessor :drawable_classes
|
22
|
+
attr_accessor :drawable_default_styles
|
23
|
+
attr_accessor :widget_classes
|
24
|
+
|
25
|
+
def inherited(subclass)
|
26
|
+
Shoes::Drawable.drawable_classes ||= []
|
27
|
+
Shoes::Drawable.drawable_classes << subclass
|
28
|
+
|
29
|
+
Shoes::Drawable.drawable_default_styles ||= {}
|
30
|
+
Shoes::Drawable.drawable_default_styles[subclass] = {}
|
31
|
+
|
32
|
+
Shoes::Drawable.widget_classes ||= []
|
33
|
+
if subclass < Shoes::Widget
|
34
|
+
Shoes::Drawable.widget_classes << subclass.name
|
35
|
+
end
|
36
|
+
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
def dsl_name
|
41
|
+
n = name.split("::").last.chomp("Drawable")
|
42
|
+
n.gsub(/(.)([A-Z])/, '\1_\2').downcase
|
43
|
+
end
|
44
|
+
|
45
|
+
def drawable_class_by_name(name)
|
46
|
+
drawable_classes.detect { |k| k.dsl_name == name.to_s }
|
47
|
+
end
|
48
|
+
|
49
|
+
def is_widget_class?(name)
|
50
|
+
!!Shoes::Drawable.widget_classes.intersect?([name.to_s])
|
51
|
+
end
|
52
|
+
|
53
|
+
def validate_as(prop_name, value)
|
54
|
+
prop_name = prop_name.to_s
|
55
|
+
hashes = shoes_style_hashes
|
56
|
+
|
57
|
+
h = hashes.detect { |hash| hash[:name] == prop_name }
|
58
|
+
raise(Shoes::Errors::NoSuchStyleError, "Can't find property #{prop_name.inspect} in #{self} property list: #{hashes.inspect}!") unless h
|
59
|
+
|
60
|
+
return value if h[:validator].nil?
|
61
|
+
|
62
|
+
h[:validator].call(value)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return a list of Shoes events for this class.
|
66
|
+
#
|
67
|
+
# @return Array[String] the list of event names
|
68
|
+
def get_shoes_events
|
69
|
+
if @shoes_events.nil?
|
70
|
+
raise UnknownEventsForClass, "Drawable type #{self.class} hasn't defined its list of Shoes events!"
|
71
|
+
end
|
72
|
+
|
73
|
+
@shoes_events
|
74
|
+
end
|
75
|
+
|
76
|
+
# Set the list of Shoes event names that are allowed for this class.
|
77
|
+
#
|
78
|
+
# @param args [Array] an array of event names, which will be coerced to Strings
|
79
|
+
# @return [void]
|
80
|
+
def shoes_events(*args)
|
81
|
+
@shoes_events ||= args.map(&:to_s) + self.superclass.get_shoes_events
|
82
|
+
end
|
83
|
+
|
84
|
+
# Assign a new Shoes Drawable ID number, starting from 1.
|
85
|
+
# This allows non-overlapping small integer IDs for Shoes
|
86
|
+
# linkable IDs - the number part of making it clear what
|
87
|
+
# widget you're talking about.
|
88
|
+
def allocate_drawable_id
|
89
|
+
@drawable_id_counter ||= 0
|
90
|
+
@drawable_id_counter += 1
|
91
|
+
@drawable_id_counter
|
92
|
+
end
|
93
|
+
|
94
|
+
def register_drawable_id(id, drawable)
|
95
|
+
@drawables_by_id ||= {}
|
96
|
+
@drawables_by_id[id] = drawable
|
97
|
+
end
|
98
|
+
|
99
|
+
def unregister_drawable_id(id)
|
100
|
+
@drawables_by_id ||= {}
|
101
|
+
@drawables_by_id.delete(id)
|
102
|
+
end
|
103
|
+
|
104
|
+
def drawable_by_id(id, none_ok: false)
|
105
|
+
val = @drawables_by_id[id]
|
106
|
+
unless val || none_ok
|
107
|
+
raise "No Drawable Found! #{@drawables_by_id.inspect}"
|
108
|
+
end
|
109
|
+
|
110
|
+
val
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def linkable_properties
|
116
|
+
@linkable_properties ||= []
|
117
|
+
end
|
118
|
+
|
119
|
+
def linkable_properties_hash
|
120
|
+
@linkable_properties_hash ||= {}
|
121
|
+
end
|
122
|
+
|
123
|
+
public
|
124
|
+
|
125
|
+
# Shoes styles in Shoes Linkables are automatically sync'd with the display side objects.
|
126
|
+
# If a block is passed to shoes_style, that's the validation for the property. It should
|
127
|
+
# convert a given value to a valid value for the property or throw an exception.
|
128
|
+
def shoes_style(name, &validator)
|
129
|
+
name = name.to_s
|
130
|
+
|
131
|
+
return if linkable_properties_hash[name]
|
132
|
+
|
133
|
+
linkable_properties << { name: name, validator: }
|
134
|
+
linkable_properties_hash[name] = true
|
135
|
+
end
|
136
|
+
|
137
|
+
# Add these names as Shoes styles
|
138
|
+
def shoes_styles(*names)
|
139
|
+
names.each { |n| shoes_style(n) }
|
140
|
+
end
|
141
|
+
|
142
|
+
def shoes_style_names
|
143
|
+
parent_prop_names = self != Shoes::Drawable ? self.superclass.shoes_style_names : []
|
144
|
+
|
145
|
+
parent_prop_names | linkable_properties.map { |prop| prop[:name] }
|
146
|
+
end
|
147
|
+
|
148
|
+
def shoes_style_hashes
|
149
|
+
parent_hashes = self != Shoes::Drawable ? self.superclass.shoes_style_hashes : []
|
150
|
+
|
151
|
+
parent_hashes + linkable_properties
|
152
|
+
end
|
153
|
+
|
154
|
+
def shoes_style_name?(name)
|
155
|
+
linkable_properties_hash[name.to_s] ||
|
156
|
+
(self != Shoes::Drawable && superclass.shoes_style_name?(name))
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Shoes uses a "hidden" style property for hide/show
|
161
|
+
shoes_style :hidden
|
162
|
+
|
163
|
+
attr_reader :debug_id
|
164
|
+
|
165
|
+
def initialize(*args, **kwargs)
|
166
|
+
log_init("Shoes::#{self.class.name}")
|
167
|
+
|
168
|
+
default_styles = Shoes::Drawable.drawable_default_styles[self.class]
|
169
|
+
|
170
|
+
self.class.shoes_style_names.each do |prop|
|
171
|
+
prop_sym = prop.to_sym
|
172
|
+
if kwargs.key?(prop_sym)
|
173
|
+
val = self.class.validate_as(prop, kwargs[prop_sym])
|
174
|
+
instance_variable_set("@" + prop, val)
|
175
|
+
elsif default_styles.key?(prop_sym)
|
176
|
+
val = self.class.validate_as(prop, default_styles[prop_sym])
|
177
|
+
instance_variable_set("@" + prop, val)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
super(linkable_id: Shoes::Drawable.allocate_drawable_id)
|
182
|
+
Shoes::Drawable.register_drawable_id(self.linkable_id, self)
|
183
|
+
|
184
|
+
generate_debug_id
|
185
|
+
end
|
186
|
+
|
187
|
+
# Calling stack.app or drawable.app will execute the block
|
188
|
+
# with the Shoes::App as self, and with that stack or
|
189
|
+
# flow as the current slot.
|
190
|
+
#
|
191
|
+
# @incompatibility In Shoes Classic this is the only way
|
192
|
+
# to change self, while Scarpe will also change self
|
193
|
+
# with the other Slot Manipulation methods: #clear,
|
194
|
+
# #append, #prepend, #before and #after.
|
195
|
+
#
|
196
|
+
# @return [Shoes::App] the Shoes app
|
197
|
+
# @yield the block to call with the Shoes App as self
|
198
|
+
def app(&block)
|
199
|
+
Shoes::App.instance.with_slot(self, &block) if block_given?
|
200
|
+
Shoes::App.instance
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
def generate_debug_id
|
206
|
+
cl = caller_locations(3)
|
207
|
+
da = cl.detect { |loc| !loc.path.include?("lacci/lib/shoes") }
|
208
|
+
@drawable_defined_at = "#{File.basename(da.path)}:#{da.lineno}"
|
209
|
+
|
210
|
+
class_name = self.class.name.split("::")[-1]
|
211
|
+
|
212
|
+
@debug_id = "#{class_name}##{@linkable_id}(#{@drawable_defined_at})"
|
213
|
+
end
|
214
|
+
|
215
|
+
public
|
216
|
+
|
217
|
+
def inspect
|
218
|
+
"#<#{debug_id} " +
|
219
|
+
" @parent=#{@parent ? @parent.debug_id : "(none)"} " +
|
220
|
+
"@children=#{@children ? @children.map(&:debug_id) : "(none)"} properties=#{shoes_style_values.inspect}>"
|
221
|
+
end
|
222
|
+
|
223
|
+
private
|
224
|
+
|
225
|
+
def validate_event_name(event_name)
|
226
|
+
unless self.class.get_shoes_events.include?(event_name.to_s)
|
227
|
+
raise Shoes::UnregisteredShoesEvent, "Drawable #{self.inspect} tried to bind Shoes event #{event_name}, which is not in #{evetns.inspect}!"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def bind_self_event(event_name, &block)
|
232
|
+
raise(Shoes::Errors::NoLinkableIdError, "Drawable has no linkable_id! #{inspect}") unless linkable_id
|
233
|
+
|
234
|
+
validate_event_name(event_name)
|
235
|
+
|
236
|
+
bind_shoes_event(event_name: event_name, target: linkable_id, &block)
|
237
|
+
end
|
238
|
+
|
239
|
+
def bind_no_target_event(event_name, &block)
|
240
|
+
validate_event_name(event_name)
|
241
|
+
|
242
|
+
bind_shoes_event(event_name:, &block)
|
243
|
+
end
|
244
|
+
|
245
|
+
public
|
246
|
+
|
247
|
+
def event(event_name, *args, **kwargs)
|
248
|
+
validate_event_name(event_name)
|
249
|
+
|
250
|
+
send_shoes_event(*args, **kwargs, event_name:, target: linkable_id)
|
251
|
+
end
|
252
|
+
|
253
|
+
def shoes_style_values
|
254
|
+
all_property_names = self.class.shoes_style_names
|
255
|
+
|
256
|
+
properties = {}
|
257
|
+
all_property_names.each do |prop|
|
258
|
+
properties[prop] = instance_variable_get("@" + prop)
|
259
|
+
end
|
260
|
+
properties["shoes_linkable_id"] = self.linkable_id
|
261
|
+
properties
|
262
|
+
end
|
263
|
+
|
264
|
+
def style(*args, **kwargs)
|
265
|
+
if args.empty? && kwargs.empty?
|
266
|
+
# Just called as .style()
|
267
|
+
shoes_style_values
|
268
|
+
elsif args.empty?
|
269
|
+
# This is called to set one or more Shoes styles
|
270
|
+
prop_names = self.class.shoes_style_names
|
271
|
+
unknown_styles = kwargs.keys.select { |k| !prop_names.include?(k.to_s) }
|
272
|
+
unless unknown_styles.empty?
|
273
|
+
raise Shoes::Errors::NoSuchStyleError, "Unknown styles for drawable type #{self.class.name}: #{unknown_styles.join(", ")}"
|
274
|
+
end
|
275
|
+
|
276
|
+
kwargs.each do |name, val|
|
277
|
+
instance_variable_set("@#{name}", val)
|
278
|
+
end
|
279
|
+
elsif args.length == 1 && args[0] < Shoes::Drawable
|
280
|
+
# Shoes supports calling .style with a Shoes class, e.g. .style(Shoes::Button, displace_left: 5)
|
281
|
+
kwargs.each do |name, val|
|
282
|
+
Shoes::Drawable.drawable_default_styles[args[0]][name.to_sym] = val
|
283
|
+
end
|
284
|
+
else
|
285
|
+
raise Shoes::Errors::InvalidAttributeValueError, "Unexpected arguments to style! args: #{args.inspect}, keyword args: #{kwargs.inspect}"
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
private
|
290
|
+
|
291
|
+
def create_display_drawable
|
292
|
+
klass_name = self.class.name.delete_prefix("Scarpe::").delete_prefix("Shoes::")
|
293
|
+
|
294
|
+
is_widget = Shoes::Drawable.is_widget_class?(klass_name)
|
295
|
+
|
296
|
+
# Should we send an event so this can be discovered from someplace other than
|
297
|
+
# the DisplayService?
|
298
|
+
::Shoes::DisplayService.display_service.create_display_drawable_for(klass_name, self.linkable_id, shoes_style_values, is_widget:)
|
299
|
+
end
|
300
|
+
|
301
|
+
public
|
302
|
+
|
303
|
+
attr_reader :parent
|
304
|
+
attr_reader :destroyed
|
305
|
+
|
306
|
+
def set_parent(new_parent)
|
307
|
+
@parent&.remove_child(self)
|
308
|
+
new_parent&.add_child(self)
|
309
|
+
@parent = new_parent
|
310
|
+
send_shoes_event(new_parent.linkable_id, event_name: "parent", target: linkable_id)
|
311
|
+
end
|
312
|
+
|
313
|
+
# Removes the element from the Shoes::Drawable tree and removes all event subscriptions
|
314
|
+
def destroy
|
315
|
+
@parent&.remove_child(self)
|
316
|
+
@parent = nil
|
317
|
+
@destroyed = true
|
318
|
+
unsub_all_shoes_events
|
319
|
+
send_shoes_event(event_name: "destroy", target: linkable_id)
|
320
|
+
Shoes::Drawable.unregister_drawable_id(linkable_id)
|
321
|
+
end
|
322
|
+
alias_method :remove, :destroy
|
323
|
+
|
324
|
+
# Hide the drawable.
|
325
|
+
def hide
|
326
|
+
self.hidden = true
|
327
|
+
end
|
328
|
+
|
329
|
+
# Show the drawable.
|
330
|
+
def show
|
331
|
+
self.hidden = false
|
332
|
+
end
|
333
|
+
|
334
|
+
# Hide the drawable if it is currently shown. Show it if it is currently hidden.
|
335
|
+
def toggle
|
336
|
+
self.hidden = !self.hidden
|
337
|
+
end
|
338
|
+
|
339
|
+
# We use method_missing to auto-create Shoes style getters and setters.
|
340
|
+
def method_missing(name, *args, **kwargs, &block)
|
341
|
+
name_s = name.to_s
|
342
|
+
|
343
|
+
if name_s[-1] == "="
|
344
|
+
prop_name = name_s[0..-2]
|
345
|
+
if self.class.shoes_style_name?(prop_name)
|
346
|
+
self.class.define_method(name) do |new_value|
|
347
|
+
raise(Shoes::Errors::NoLinkableIdError, "Trying to set Shoes styles in an object with no linkable ID! #{inspect}") unless linkable_id
|
348
|
+
|
349
|
+
new_value = self.class.validate_as(prop_name, new_value)
|
350
|
+
instance_variable_set("@" + prop_name, new_value)
|
351
|
+
send_shoes_event({ prop_name => new_value }, event_name: "prop_change", target: linkable_id)
|
352
|
+
end
|
353
|
+
|
354
|
+
return self.send(name, *args, **kwargs, &block)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
if self.class.shoes_style_name?(name_s)
|
359
|
+
self.class.define_method(name) do
|
360
|
+
raise(Shoes::Errors::NoLinkableIdError, "Trying to get Shoes styles in an object with no linkable ID! #{inspect}") unless linkable_id
|
361
|
+
|
362
|
+
instance_variable_get("@" + name_s)
|
363
|
+
end
|
364
|
+
|
365
|
+
return self.send(name, *args, **kwargs, &block)
|
366
|
+
end
|
367
|
+
|
368
|
+
super(name, *args, **kwargs, &block)
|
369
|
+
end
|
370
|
+
|
371
|
+
def respond_to_missing?(name, include_private = false)
|
372
|
+
name_s = name.to_s
|
373
|
+
return true if self.class.shoes_style_name?(name_s)
|
374
|
+
return true if self.class.shoes_style_name?(name_s[0..-2]) && name_s[-1] == "="
|
375
|
+
return true if Drawable.drawable_class_by_name(name_s)
|
376
|
+
|
377
|
+
super
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|