shoes-core 4.0.0.pre11 → 4.0.0.pre12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/shoes/app.rb +13 -6
- data/lib/shoes/button.rb +1 -4
- data/lib/shoes/check_button.rb +1 -4
- data/lib/shoes/common/focus.rb +10 -0
- data/lib/shoes/common/hover.rb +4 -0
- data/lib/shoes/configuration.rb +6 -1
- data/lib/shoes/core/version.rb +1 -1
- data/lib/shoes/dialog.rb +2 -2
- data/lib/shoes/dsl.rb +1 -0
- data/lib/shoes/dsl/art.rb +8 -0
- data/lib/shoes/input_box.rb +1 -4
- data/lib/shoes/list_box.rb +1 -0
- data/lib/shoes/mock/slot.rb +1 -0
- data/lib/shoes/oval.rb +2 -2
- data/lib/shoes/ui/cli.rb +4 -2
- data/lib/shoes/ui/cli/base_command.rb +14 -3
- data/lib/shoes/ui/cli/default_command.rb +2 -13
- data/lib/shoes/ui/cli/help_command.rb +2 -2
- data/lib/shoes/ui/cli/manual_command.rb +1 -1
- data/lib/shoes/ui/cli/package_command.rb +2 -2
- data/lib/shoes/ui/cli/samples_command.rb +42 -0
- data/lib/shoes/ui/cli/select_backend_command.rb +1 -1
- data/lib/shoes/ui/cli/version_command.rb +1 -1
- data/lib/shoes/version.rb +1 -1
- data/lib/shoes/widget.rb +50 -27
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 661b33d7f533fcfeca029b50a73d2825fd861b9cb3dd22b802407c2c9530982a
|
4
|
+
data.tar.gz: 1910df83d014768abe2dbd8fbc4475b857ca9c84c96e85506aa8db8572ce891d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6955fbb19afbb56560f631f52c139ae43420da5c16cfd4f2cc85bb42114af6fbec7db5f49697fb9ce977f8a64b1b7d6425a4406f7580ed8fd0de0fb13cd56527
|
7
|
+
data.tar.gz: 1d9df772049d179b46c3a70a92c5309a09495812607cdbdc9b4366040d778652dabca4c807e9ff2e2eaf2f8def21f0f335541ee6072f44dfef1ff48e7d0183ab
|
data/lib/shoes/app.rb
CHANGED
@@ -69,12 +69,16 @@ class Shoes
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def quit
|
72
|
+
Shoes.apps.each(&:close)
|
73
|
+
end
|
74
|
+
|
75
|
+
alias exit quit
|
76
|
+
|
77
|
+
def close
|
72
78
|
Shoes.unregister self
|
73
79
|
@__app__.quit
|
74
80
|
end
|
75
81
|
|
76
|
-
alias close quit
|
77
|
-
|
78
82
|
def parent
|
79
83
|
@__app__.current_slot.parent
|
80
84
|
end
|
@@ -123,16 +127,19 @@ class Shoes
|
|
123
127
|
inspect_details
|
124
128
|
end
|
125
129
|
|
126
|
-
DELEGATE_BLACKLIST = [:parent, :app].freeze
|
127
|
-
|
128
130
|
# class definitions are evaluated top to bottom, want to have all of them
|
129
131
|
# so define at bottom
|
130
132
|
DELEGATE_METHODS = ((Shoes::App.public_instance_methods(false) +
|
131
|
-
Shoes::DSL.public_instance_methods)
|
133
|
+
Shoes::DSL.public_instance_methods)).freeze
|
132
134
|
|
133
135
|
def self.subscribe_to_dsl_methods(klazz)
|
136
|
+
# Delegate anything in the app/dsl public list that DOESN'T have a method
|
137
|
+
# already defined on the class in question
|
138
|
+
methods_to_delegate = DELEGATE_METHODS - klazz.public_instance_methods
|
139
|
+
|
134
140
|
klazz.extend Forwardable unless klazz.is_a? Forwardable
|
135
|
-
klazz.def_delegators :app, *
|
141
|
+
klazz.def_delegators :app, *methods_to_delegate
|
142
|
+
|
136
143
|
@method_subscribers ||= []
|
137
144
|
@method_subscribers << klazz
|
138
145
|
end
|
data/lib/shoes/button.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
class Shoes
|
3
3
|
class Button < Common::UIElement
|
4
4
|
include Common::Clickable
|
5
|
+
include Common::Focus
|
5
6
|
include Common::State
|
6
7
|
|
7
8
|
# We don't actually support release from buttons, but want to use the
|
@@ -14,10 +15,6 @@ class Shoes
|
|
14
15
|
styles[:text] = text || 'Button'
|
15
16
|
end
|
16
17
|
|
17
|
-
def focus
|
18
|
-
@gui.focus
|
19
|
-
end
|
20
|
-
|
21
18
|
def text=(value)
|
22
19
|
style(text: value.to_s)
|
23
20
|
@gui.text = value.to_s
|
data/lib/shoes/check_button.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
class Shoes
|
3
3
|
class CheckButton < Common::UIElement
|
4
4
|
include Common::Clickable
|
5
|
+
include Common::Focus
|
5
6
|
include Common::State
|
6
7
|
|
7
8
|
def checked?
|
@@ -12,10 +13,6 @@ class Shoes
|
|
12
13
|
style(checked: value)
|
13
14
|
@gui.checked = value
|
14
15
|
end
|
15
|
-
|
16
|
-
def focus
|
17
|
-
@gui.focus
|
18
|
-
end
|
19
16
|
end
|
20
17
|
|
21
18
|
class Check < CheckButton
|
data/lib/shoes/common/hover.rb
CHANGED
data/lib/shoes/configuration.rb
CHANGED
@@ -47,7 +47,7 @@ class Shoes
|
|
47
47
|
# @example
|
48
48
|
# Shoes.configuration.backend_class(shoes_button) # => Shoes::Swt::Button
|
49
49
|
def backend_class(object)
|
50
|
-
klazz = object.is_a?(Class) ? object : object
|
50
|
+
klazz = object.is_a?(Class) ? object : find_shoes_base_class(object)
|
51
51
|
class_name = klazz.name.split("::").last
|
52
52
|
# Lookup with false to not consult modules higher in the chain Object
|
53
53
|
# because Shoes::Swt.const_defined? 'Range' => true
|
@@ -55,6 +55,11 @@ class Shoes
|
|
55
55
|
backend.const_get(class_name, false)
|
56
56
|
end
|
57
57
|
|
58
|
+
def find_shoes_base_class(object)
|
59
|
+
return object.shoes_base_class if object.respond_to?(:shoes_base_class)
|
60
|
+
object.class
|
61
|
+
end
|
62
|
+
|
58
63
|
# Creates an appropriate backend object, passing along additional
|
59
64
|
# arguments
|
60
65
|
#
|
data/lib/shoes/core/version.rb
CHANGED
data/lib/shoes/dialog.rb
CHANGED
data/lib/shoes/dsl.rb
CHANGED
@@ -60,6 +60,7 @@ require 'shoes/common/attachable'
|
|
60
60
|
require 'shoes/common/changeable'
|
61
61
|
require 'shoes/common/clickable'
|
62
62
|
require 'shoes/common/fill'
|
63
|
+
require 'shoes/common/focus'
|
63
64
|
require 'shoes/common/hover'
|
64
65
|
require 'shoes/common/link_finder'
|
65
66
|
require 'shoes/common/positioning'
|
data/lib/shoes/dsl/art.rb
CHANGED
@@ -251,6 +251,14 @@ EOS
|
|
251
251
|
|
252
252
|
create Shoes::Shape, left, top, opts, blk
|
253
253
|
end
|
254
|
+
|
255
|
+
def mask(*_)
|
256
|
+
raise Shoes::NotImplementedError,
|
257
|
+
<<~EOS
|
258
|
+
Sorry mask is not supported currently in Shoes 4!
|
259
|
+
Check out github issue #527 for any changes/updates or if you want to help :)
|
260
|
+
EOS
|
261
|
+
end
|
254
262
|
end
|
255
263
|
end
|
256
264
|
end
|
data/lib/shoes/input_box.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
class Shoes
|
3
3
|
class InputBox < Common::UIElement
|
4
4
|
include Common::Changeable
|
5
|
+
include Common::Focus
|
5
6
|
include Common::State
|
6
7
|
|
7
8
|
def before_initialize(styles, text)
|
@@ -13,10 +14,6 @@ class Shoes
|
|
13
14
|
update_visibility
|
14
15
|
end
|
15
16
|
|
16
|
-
def focus
|
17
|
-
@gui.focus
|
18
|
-
end
|
19
|
-
|
20
17
|
def text
|
21
18
|
@gui.text
|
22
19
|
end
|
data/lib/shoes/list_box.rb
CHANGED
data/lib/shoes/mock/slot.rb
CHANGED
data/lib/shoes/oval.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
class Shoes
|
3
3
|
class Oval < Common::ArtElement
|
4
|
-
style_with :art_styles, :center, :common_styles, :dimensions
|
4
|
+
style_with :art_styles, :center, :common_styles, :dimensions
|
5
5
|
STYLES = { fill: Shoes::COLORS[:black] }.freeze
|
6
6
|
|
7
7
|
def create_dimensions(left, top, width, height)
|
8
8
|
left ||= @style[:left] || 0
|
9
9
|
top ||= @style[:top] || 0
|
10
|
-
width ||= @style[:
|
10
|
+
width ||= @style[:width] || @style[:diameter] || (@style[:radius] || 0) * 2
|
11
11
|
height ||= @style[:height] || width
|
12
12
|
|
13
13
|
@dimensions = AbsoluteDimensions.new left, top, width, height, @style
|
data/lib/shoes/ui/cli.rb
CHANGED
@@ -7,6 +7,7 @@ require 'shoes/ui/cli/default_command'
|
|
7
7
|
require 'shoes/ui/cli/help_command'
|
8
8
|
require 'shoes/ui/cli/manual_command'
|
9
9
|
require 'shoes/ui/cli/package_command'
|
10
|
+
require 'shoes/ui/cli/samples_command'
|
10
11
|
require 'shoes/ui/cli/select_backend_command'
|
11
12
|
require 'shoes/ui/cli/version_command'
|
12
13
|
|
@@ -17,6 +18,7 @@ class Shoes
|
|
17
18
|
help: HelpCommand,
|
18
19
|
manual: ManualCommand,
|
19
20
|
package: PackageCommand,
|
21
|
+
samples: SamplesCommand,
|
20
22
|
select_backend: SelectBackendCommand,
|
21
23
|
version: VersionCommand,
|
22
24
|
}.freeze
|
@@ -29,7 +31,7 @@ class Shoes
|
|
29
31
|
|
30
32
|
def run(args)
|
31
33
|
if args.empty?
|
32
|
-
Shoes::UI::CLI::HelpCommand.new
|
34
|
+
Shoes::UI::CLI::HelpCommand.new.run
|
33
35
|
exit(1)
|
34
36
|
end
|
35
37
|
|
@@ -39,7 +41,7 @@ class Shoes
|
|
39
41
|
|
40
42
|
def create_command(*args)
|
41
43
|
command_class = SUPPORTED_COMMANDS[args.first.to_sym] || DefaultCommand
|
42
|
-
command_class.new(args.dup)
|
44
|
+
command_class.new(*args.dup)
|
43
45
|
end
|
44
46
|
end
|
45
47
|
end
|
@@ -5,7 +5,7 @@ class Shoes
|
|
5
5
|
class BaseCommand
|
6
6
|
attr_reader :args
|
7
7
|
|
8
|
-
def initialize(args)
|
8
|
+
def initialize(*args)
|
9
9
|
@args = args
|
10
10
|
end
|
11
11
|
|
@@ -16,11 +16,22 @@ class Shoes
|
|
16
16
|
Shoes.logger.warn("Unexpected extra parameters '#{unexpected}'")
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
19
|
+
def parse!(args)
|
20
|
+
options.parse!(args)
|
21
|
+
true
|
22
|
+
rescue OptionParser::InvalidOption => e
|
23
|
+
puts "Whoops! #{e.message}"
|
24
|
+
puts
|
25
|
+
puts help
|
26
|
+
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def help
|
20
31
|
nil
|
21
32
|
end
|
22
33
|
|
23
|
-
def
|
34
|
+
def help_from_options(command, options)
|
24
35
|
lines = ["#{command}\n"] + options.summarize
|
25
36
|
lines.join("")
|
26
37
|
end
|
@@ -16,18 +16,7 @@ class Shoes
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
20
|
-
self.class.options.parse!(args)
|
21
|
-
true
|
22
|
-
rescue OptionParser::InvalidOption => e
|
23
|
-
puts "Whoops! #{e.message}"
|
24
|
-
puts
|
25
|
-
puts self.class.help
|
26
|
-
|
27
|
-
false
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.options
|
19
|
+
def options
|
31
20
|
OptionParser.new do |opts|
|
32
21
|
# Keep around command dashed options
|
33
22
|
opts.on('-v', '--version', 'Shoes version') do
|
@@ -47,7 +36,7 @@ class Shoes
|
|
47
36
|
end
|
48
37
|
end
|
49
38
|
|
50
|
-
def
|
39
|
+
def help
|
51
40
|
help_from_options("shoes [options] file", options)
|
52
41
|
end
|
53
42
|
end
|
@@ -13,7 +13,7 @@ class Shoes
|
|
13
13
|
command_classes.concat(SUPPORTED_COMMANDS.map(&:last))
|
14
14
|
|
15
15
|
command_classes.each do |command_class|
|
16
|
-
text = command_class.help.to_s
|
16
|
+
text = command_class.new([]).help.to_s
|
17
17
|
unless text.empty?
|
18
18
|
puts text
|
19
19
|
puts
|
@@ -21,7 +21,7 @@ class Shoes
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
24
|
+
def help
|
25
25
|
<<-EOS
|
26
26
|
shoes help
|
27
27
|
Displays this help text
|
@@ -14,12 +14,12 @@ class Shoes
|
|
14
14
|
rescue OptionParser::InvalidOption => e
|
15
15
|
puts "Whoops! #{e.message}"
|
16
16
|
puts
|
17
|
-
puts
|
17
|
+
puts help
|
18
18
|
|
19
19
|
false
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
22
|
+
def help
|
23
23
|
help_from_options("shoes package [options] file",
|
24
24
|
Shoes::Packager.new.options) + <<-EOS
|
25
25
|
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'fileutils'
|
3
|
+
require 'shoes/samples'
|
4
|
+
|
5
|
+
class Shoes
|
6
|
+
module UI
|
7
|
+
class CLI
|
8
|
+
class SamplesCommand < BaseCommand
|
9
|
+
attr_accessor :destination_dir
|
10
|
+
|
11
|
+
def run
|
12
|
+
if parse!(args)
|
13
|
+
source = Shoes::Samples.path
|
14
|
+
destination = File.join((destination_dir || Dir.pwd), "shoes_samples")
|
15
|
+
|
16
|
+
if File.exist?(destination)
|
17
|
+
puts "Oops, #{destination} already exists! Try somewhere else, maybe with -d."
|
18
|
+
else
|
19
|
+
FileUtils.cp_r source, destination
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def options
|
25
|
+
OptionParser.new do |opts|
|
26
|
+
opts.on('-dDEST', '--destination=DEST', 'Destination directory') do |destination|
|
27
|
+
self.destination_dir = destination
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def help
|
33
|
+
help_from_options("shoes samples [options]",
|
34
|
+
options) + <<-EOS
|
35
|
+
|
36
|
+
Installs samples to try out.
|
37
|
+
EOS
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/shoes/version.rb
CHANGED
data/lib/shoes/widget.rb
CHANGED
@@ -2,17 +2,22 @@
|
|
2
2
|
class Shoes
|
3
3
|
# This is the superclass for creating custom Shoes widgets.
|
4
4
|
#
|
5
|
-
# To use, inherit from {Shoes::Widget}
|
5
|
+
# To use, inherit from {Shoes::Widget} and implement a initialize_widget
|
6
|
+
# method. You get a few magical effects:
|
6
7
|
#
|
7
8
|
# * When you inherit from {Shoes::Widget}, you get a method in your Apps to
|
8
9
|
# create your widgets. The method is lower- and snake-cased. It returns
|
9
10
|
# an instance of your widget class.
|
11
|
+
#
|
10
12
|
# * Your widgets delegate missing methods to their app object. This
|
11
13
|
# allows you to use the Shoes DSL within your widgets.
|
12
14
|
#
|
15
|
+
# * Your widget otherwise behaves like a flow. If the final parameter to
|
16
|
+
# the widget method is a Hash, that will initialize the flow as well.
|
17
|
+
#
|
13
18
|
# @example
|
14
19
|
# class SayHello < Shoes::Widget
|
15
|
-
# def
|
20
|
+
# def initialize_widget word
|
16
21
|
# para "Hello #{word}", stroke: green, size: 80
|
17
22
|
# end
|
18
23
|
# end
|
@@ -21,40 +26,47 @@ class Shoes
|
|
21
26
|
# say_hello 'Shoes'
|
22
27
|
# end
|
23
28
|
#
|
24
|
-
class Widget
|
25
|
-
include Common::Inspect
|
26
|
-
|
29
|
+
class Widget < Shoes::Flow
|
27
30
|
Shoes::App.subscribe_to_dsl_methods self
|
28
31
|
|
29
|
-
attr_accessor :
|
30
|
-
attr_writer :app
|
32
|
+
attr_accessor :original_args
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
+
# Having Widget define initialize makes it easier for us to detect whether
|
35
|
+
# subclasses have inappropriately overridden it or not.
|
36
|
+
def initialize(*_)
|
37
|
+
super
|
34
38
|
end
|
35
39
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
+
def initialize_widget
|
41
|
+
# Expected to be overridden by children but not guaranteed
|
42
|
+
end
|
43
|
+
|
44
|
+
def shoes_base_class
|
45
|
+
Shoes::Widget
|
40
46
|
end
|
41
47
|
|
42
48
|
def self.inherited(klass, &_blk)
|
49
|
+
# Ensure Hover styling class exists, but Widget gets hover behavior from parents
|
50
|
+
Shoes::Common::Hover.create_hover_class(klass)
|
51
|
+
|
43
52
|
dsl_method = dsl_method_name(klass)
|
44
|
-
Shoes::App.new_dsl_method(dsl_method) do |*args
|
45
|
-
#
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
#
|
50
|
-
|
51
|
-
|
52
|
-
#
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
Shoes::App.new_dsl_method(dsl_method) do |*args|
|
54
|
+
# Old-school widgets that try to use initialize instead of
|
55
|
+
# initialize_widget will get a warning before they likely fail on new.
|
56
|
+
klass.warn_about_initialize
|
57
|
+
|
58
|
+
# If last arg is a Hash, pass that to the underlying Flow
|
59
|
+
container_args = args.last.is_a?(Hash) ? args.last : {}
|
60
|
+
|
61
|
+
# Expected to call through to initialize our underlying slot behavior
|
62
|
+
widget_instance = klass.new(@__app__, @__app__.current_slot, container_args)
|
63
|
+
|
64
|
+
# Call the user's widget initialization, with proper slot context
|
65
|
+
old_current_slot = @__app__.current_slot
|
66
|
+
@__app__.current_slot = widget_instance
|
67
|
+
widget_instance.initialize_widget(*args)
|
68
|
+
@__app__.current_slot = old_current_slot
|
69
|
+
|
58
70
|
widget_instance
|
59
71
|
end
|
60
72
|
end
|
@@ -64,5 +76,16 @@ class Shoes
|
|
64
76
|
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
65
77
|
.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
66
78
|
end
|
79
|
+
|
80
|
+
def self.warn_about_initialize
|
81
|
+
if instance_method(:initialize).owner != Shoes::Widget
|
82
|
+
Shoes.logger.warn <<~EOS
|
83
|
+
You've defined an `initialize` method on class '#{self}'. This is no longer supported.
|
84
|
+
Your widget likely won't display, and you'll potentially receive argument errors.
|
85
|
+
|
86
|
+
Instead, define `initialize_widget` and we'll call it from your generated widget method.
|
87
|
+
EOS
|
88
|
+
end
|
89
|
+
end
|
67
90
|
end
|
68
91
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shoes-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.0.0.
|
4
|
+
version: 4.0.0.pre12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Team Shoes
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-10-02 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Shoes is the best little GUI toolkit for Ruby. Shoes makes building for
|
14
14
|
Mac, Windows, and Linux super simple. This is the DSL for writing your app. You'll
|
@@ -45,6 +45,7 @@ files:
|
|
45
45
|
- lib/shoes/common/changeable.rb
|
46
46
|
- lib/shoes/common/clickable.rb
|
47
47
|
- lib/shoes/common/fill.rb
|
48
|
+
- lib/shoes/common/focus.rb
|
48
49
|
- lib/shoes/common/hover.rb
|
49
50
|
- lib/shoes/common/image_handling.rb
|
50
51
|
- lib/shoes/common/inspect.rb
|
@@ -149,6 +150,7 @@ files:
|
|
149
150
|
- lib/shoes/ui/cli/help_command.rb
|
150
151
|
- lib/shoes/ui/cli/manual_command.rb
|
151
152
|
- lib/shoes/ui/cli/package_command.rb
|
153
|
+
- lib/shoes/ui/cli/samples_command.rb
|
152
154
|
- lib/shoes/ui/cli/select_backend_command.rb
|
153
155
|
- lib/shoes/ui/cli/version_command.rb
|
154
156
|
- lib/shoes/ui/picker.rb
|
@@ -180,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
180
182
|
version: 1.3.1
|
181
183
|
requirements: []
|
182
184
|
rubyforge_project:
|
183
|
-
rubygems_version: 2.6.
|
185
|
+
rubygems_version: 2.6.13
|
184
186
|
signing_key:
|
185
187
|
specification_version: 4
|
186
188
|
summary: The best little DSL for the best little GUI toolkit for Ruby.
|