Martin DeMello's Gooey Challenge
Martin DeMello: One of the most interesting facets of a desktop GUI system is how easy it makes it to go off the beaten track, particularly how well you can add “first class” components to the system. (Using ‘first class’ here to mean ‘on an equal footing with the widgets supplied by the toolkit’). Also, as a ruby programmer, I’d naturally rather not drop down into C (or Java) to do this.
So how does Shoes hold up against this challenge? And i mean: without hacking anything new into Shoes, using syntax introduced before the challenge was posed.
Well, let’s go over the four parts of the contest:
- A component consisting of a series of existing components hooked together to act as a single widget. (Such as an
icon
widget that incorporates a picture and a textfield. With options to turn off or size the image and make the text editable.) - A component built ‘from scratch’ atop a canvas, that is, handling its own drawing and event management. (Like a speedometer-type dial with a configurable range and tick interval.)
- A component combining a canvas and existing widgets. (For example, a box that holds a component and paints a customised border around it.)
- A container that takes a collection of widgets and lays them out according to some userdefined algorithm. (He suggests a pure-ruby implementation of a wrapbox, but I thought this point might be better illustrated by a cascading container.)
My initial response had a few simple code examples, but I didn’t take the chance to do all the examples just as Martin described. That e-mail touches on how Shoes custom widgets work.
Now I’ve had a minute and I’d like to present just my new, unadorned entries. In each of these, a custom Shoes widget is setup by inheriting from the Widget
class. And Shoes then creates a method using the lowercased name of the class which is used in the app
. (And you can try these out with today’s super-fresh recent builds.)
Icon Widget (challenge1.rb
)
class Icon < Widget
attr_accessor :image, :caption
def initialize opts = {}
@stack = stack
@image = @stack.image(*opts[:image]) if opts[:image]
@caption = @stack.para(*opts[:text])
end
def edit
return if @edit
@caption.hide
@stack.append do
@edit = edit_line :width => 200, :text => @caption
end
end
def save
return unless @edit
@caption.replace @edit.text
@edit.remove
@caption.show
end
end
Shoes.app do
stack do
@icon = icon :image => "static/shoes-icon.png",
:text => "Welcome!"
button("image.hide") { @icon.image.hide }
button("image.show") { @icon.image.show }
button("image.size") { @icon.image.style :width => 64, :height => 64 }
button("text.edit") { @icon.edit }
button("text.save") { @icon.save }
end
end
Speedometer Widget (challenge2.rb
)
class Speedometer < Widget
attr_accessor :range, :tick, :position
def initialize opts = {}
@range = opts[:range] || 200
@tick = opts[:tick] || 10
@position = opts[:position] || 0
@cx, @cy = self.left + 110, self.top + 100
nostroke
rect :top => self.top, :left => self.left,
:width => 220, :height => 200
nofill
stroke white
oval :left => @cx - 50, :top => @cy - 50, :radius => 50
(ticks + 1).times do |i|
radial_line 225 + ((270.0 / ticks) * i), 70..80
radial_line 225 + ((270.0 / ticks) * i), 45..49
end
strokewidth 2
oval :left => @cx - 70, :top => @cy - 70, :radius => 70
stroke lightgreen
oval :left => @cx - 5, :top => @cy - 5, :radius => 5
@needle = radial_line 225 + ((270.0 / @range) * @position), 0..90
end
def ticks; @range / @tick end
def radial_line deg, r
pos = ((deg / 360.0) * (2.0 * Math::PI)) - (Math::PI / 2.0)
line (Math.cos(pos) * r.begin) + @cx, (Math.sin(pos) * r.begin) + @cy,
(Math.cos(pos) * r.end) + @cx, (Math.sin(pos) * r.end) + @cy
end
def position= pos
@position = pos
@needle.remove
append do
@needle = radial_line 225 + ((270.0 / @range) * @position), 0..90
end
end
end
Shoes.app do
stack do
para "Enter a number between 0 and 100"
flow do
@p = edit_line
button "OK" do
@s.position = @p.text.to_i
end
end
@s = speedometer :range => 100, :ticks => 10
end
end
Native Button with Custom Border (challenge3.rb
)
class BorderButton < Widget
def initialize *args, &blk
opts = args.detect { |a| a.is_a? Hash }
border opts[:border], :strokewidth => opts[:strokewidth]
args[args.index(opts)] = opts.
merge(:width => opts[:width] - (opts[:strokewidth] * 2))
stack(:margin => opts[:strokewidth]).button *args, &blk
end
end
Shoes.app do
borderbutton "OK", :width => 200,
:strokewidth => 4, :border => "#000".."#FFF" do
alert("PROOF!")
end
end
Cascading Container (challenge4.rb
)
class Cascade < Widget
def initialize &blk
instance_eval &blk
end
def draw(a,b)
x, y = 0, 0
contents.each do |e|
if x != e.left && y != e.top
e.move x, y
end
x += e.height
y += e.width
end
super(a,b)
end
end
Shoes.app do
cascade do
button "1"
button "2"
button "3"
end
end
Now begin the comments …
Awesome!
I’m giving it a try right now…
kamran
This is very cool
David
What happened to Hoodwink’d? Why is http://hoodwink.d sending me to a site titled “Online Generic Viagra”?
cygo
A competitor?
http://www.sproutcore.com
Axis
In addition to a repository of Shoes apps, how about a repository of Shoes widgets? That could keep the core of Shoes simple, while also allowing you to go shopping for all those exotic shoes that other toolkits have.
Comments are closed for this entry.