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:
icon widget that incorporates a picture and a textfield. With options to turn off or size the image and make the text editable.)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.)
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
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 => 100
(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 => 140
stroke lightgreen
oval :left => @cx - 5, :top => @cy - 5, :radius => 10
@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
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
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
No comments yet.
You must be logged in to add your own comment.