October 15, 2007.
So, I have been playing with Ruby a bit more recently, and I really wanted a GUI to play with too. I have had Why the Lucky Stiff in my RSS reader for a while, and I had been absently reading about his current project, Shoes, which is a GUI toolkit for Ruby. After using it for a couple of days, I'd say its worth a look.
So grammatically "What is Shoes?" hurts me slightly, but I think it is correct in this case. Regardless, Shoes is a minimalistic Graphical User Interface toolkit for the Ruby programming language, which runs on OS X, Windows, and Linux. The project is lead/inspired/spearheaded/conducted/programmed single-handedly/? by Why. You should be reassured by his self-description on his site: "Why the lucky stiff is a fledging freelance professor, one will die young and make no lasting impression."
Shoes' philosophy is minimalism, and is strongly influenced by internet design. It, like most Ruby projects I have run into, isn't designed to be everything to everyone. It has made some decisions about what it wants to be (the approved buzzword here is "opinionated software"), and if you want a simple toolkit that is fun and easy to work with, you should give Shoes a try. At this point it is too immature for a production quality project, but it is great for throwing together a GUI for your current project in a couple of minutes. It is also effective for prototyping. And, although I recommend taking your daily recommended value of salt before considering this, I think it will eventually be a very pleasant way to release simple functional GUIs along with your software. Frankly, some of the graphical stuff that Shoes can do is pretty impressive already. Eventually, maybe half a year or so, it may even have formal documentation.
According to Why it also takes some inspiration from Processing, like making it easy to draw and use graphics in your programs. However, where Processing often made me uncomfortable (allowing restricted access to a restrictive programming language; specifically Java pre-1.5), Shoes is not imposing the same awkward limitations.
Essentially, Shoes is a neat little GUI toolkit that is fun enough to play with to devote a couple of hours to. Its the quickest way to create a useful GUI that I have encountered, and it is intuitive to boot. Not a bad investment of your time.
You will need to grab a copy of Shoes. The easiest way to do this is to go here and to download the most recent snapshot. The one that is used in this tutorial is the 2007 September 06 snapshot. You can also build your own copy of Shoes from the svn repository, but that isn't really necessary at the moment (and Shoes is in a state of heavy flux, so its ever so slightly unpredictable what will emerge from the depths of the repository).
Extract the tar.gz file containing shoes, and you'll be rewarded with a shiny Shoes.app file. Throw that into your applications folder (you really don't have to do that, but its where I'll be assuming it is).
So, lets look a bit at what we have just installed: Shoes.app is similar to Wish in TK, and is the environment in which your programs will be running. Inside Shoes.app (at Shoes.app/Contents/MacOS) there is a program named 'shoes' which is the program that you will use to run your programs in Shoes.app. So this all makes perfect sense, right? Great.
...so maybe this isn't totally obvious. But it'll make more sense as we work with it.
The first thing you will want to do is add the shoes program to your shell path. It isn't enough to simply throw a symbolic link into /bin, because shoes looks for certain contingent files using relative paths, which the symbolic link will ravish. Instead I simply added this line to my ~/.profile .
And now when you type
It should throw open a browsing window for you to select a file to interpret. You can also just supply the file almost as you'd expect
You will have to use absolute paths to describe the file you want to run, relative paths will begin from the directory containing shoes, regardless of where you are actually calling it from (a wee bit awkward, but it needs to have access to its other library components).
Okay, now that we have setup our environment lets run a few of the examples.
Pretty neat. Although the stories are extraordinarily odd. The sort of thing you couldn't simply sit down and write all at once, but that you have to slowly collect over time. Okay, another example.
Also neat. See how it is printing the location of your cursor to the terminal.
A very simple little drawing program. I remember seeing a similar example done in the Swing GUI toolkit for Java in my Software Development class. I suspect that program was longer than 13 lines. Just a theory.
The other samples are worth looking at too. Then look at the actual sample files. They just aren't that complex or difficult. These are not technological demonstrations of what a master can achieve, these are things you can be doing with a little bit of effort.
Now that we have seen some examples, lets build something new. First we'll need to make a new file, lets call it example.rb. Throw it anywhere, but make sure you know the absolute path to it (I have it in the folder ~/programming/ruby/shoes/, but thats just a personal choice).
First we begin with the simplest Shoes program we can:
Shoes.app do end
Now, lets run our program and see what happens.
And (drum roll) an empty window appears. (If you don't see it, then it may be hidden behind some other windows, Shoes windows don't seem to pop to the front of the screen at this point.) Good. That means things are working. Now close Shoes and lets start building something.
First lets make a drop down box with three choices: oval, rectangle, and triangle.
shape = nil Shoes.app do shape = list_box :items => ["Square", "Oval", "Rectangle"]
Run it again and you'll see a drop down box with the three choices in the Shoes window. After pondering its significance feel free to close Shoes again.
Now lets add a button to the mix. The button will report the value of the drop down box.
shape = nil Shoes.app do shape = list_box :items => ["Square", "Oval", "Rectangle"] button "Report" do Shoes.p [shape.text] end end
Now fire up the program and you'll see when you click the button that the current value of the drop down box is sent to the terminal.
One of the most painful parts of building a GUI with bad tools is positioning your elements. Shoes has a very flexible and powerful idea for how this can be done: stacks and flows. Stacks are containers that build downward, and flows are containers that build rightward and then downward. Flows are like words in a book. Stacks are like entries in a log file. The main Shoes window is a flow, and a stack or flow can have any number of stacks and flows inside of it.
For the most part this system works out pretty well, and I imagine that as Shoes becomes increasingly refined it will work "as you expect it to" more often and be less unpredictable. Even now its almost great, but since a good GUI has to be perfect, I find myself fighting the stacks and flows a bit more than I'd like to be.
So we can add some shape to our program like this:
shape = nil Shoes.app do stack :width => 200, :height => 200, :margin => 50 do shape = list_box :items => ["Square", "Oval", "Rectangle"] button "Report" do Shoes.p [shape.text] end text "Just some filler text" end flow :margin => 10 do text "Text" text "More" text "Less" end end
I'd recommend playing around with different combinations of stacks and flows. To build more complex layouts you can do things like this:
stack do stack do flow do end end end
Now lets add some drawing to our program. It should be noted that a big part of the code here is very much borrowed from the follow.rb in the samples folder. But, we'll work to evolve it a bit further.
trails = [[0,0]] * 60 shape = nil Shoes.app do stack :width => 200, :height => 200 do shape = list_box :items => ["Square", "Oval"] end stack :width => 200, :height => 200 do nostroke fill rgb(0x30, 0xFF, 0xFF, 0.6) animate(24) do trails.shift trails << self.mouse[1,2] clear do trails.each_with_index do |(x, y), i| i += 1 oval :left => x, :top => y, :radius => i, :center => true end end end end end
The trails array is used to track the recent positions of the cursor. We are jettisoning the button, but are keeping the drop down menu. Go ahead and run this code and you'll see that it is quite simple, just a drop down menu and some circles chasing your mouse.
Okay. Now lets add a little bit more.
Replace the line:
oval :left => x, :top => y, :radius => i, :center => true
with these lines:
case shape.text when "Square" rect :left => x, :top => y, :width => i, :height => i else oval :left => x, :top => y, :radius => i, :center => true end
Now run the program again. Move the mouse around and you'll still have the circles chasing your cursor. But go up to the drop-down menu and select "Square" and you'll have squares chasing you instead. Neat.
Having a drop-down menu to select shapes is okay, but I think I'd rather use the keyboard. Lets start out with this code, taken from the previous example:
trails = [[0,0]] * 60 shape = 0 Shoes.app do nostroke fill rgb(0x30, 0xFF, 0xFF, 0.6) animate(24) do trails.shift trails << self.mouse[1,2] clear do trails.each_with_index do |(x, y), i| i += 1 case shape%2 when 0 rect :left => x, :top => y, :width => i, :height => i, :center => true else oval :left => x, :top => y, :radius => i, :center => true end end end end end
Now, this is exactly what we need... except that it has no keyboard input. Lets fix that.
Throw this code in right after the "Shoes.app do" line:
keypress do |k| case k when " " shape += 1 end end
And now run your program again. When you press spacebar the shape will toggle from a circle to a square.
(There is an occasional bug getting thrown here by this final version of the sample program, I think it has something to do with the rect method in Shoes. I may try to track this down in the near future. For the time being, this bug doesn't interfere with getting the idea of how keystrokes work, and it helps to reinforce the point that Shoes is in heavy development.)
Here is the final version of the program:
trails = [[0,0]] * 60 shape = 0 Shoes.app do keypress do |k| case k when " " shape += 1 end end nostroke fill rgb(0x30, 0xFF, 0xFF, 0.6) animate(24) do trails.shift trails << self.mouse[1,2] clear do trails.each_with_index do |(x, y), i| i += 1 case shape%2 when 0 rect :left => x, :top => y, :width => i, :height => i else oval :left => x, :top => y, :radius => i, :center => true end end end end end
Well, we've spent some time playing with Shoes, and built some simple programs. To really get a feel for Shoes, though, you need to start playing with it yourself. Head over to the Shoes project page and take a look, or just start changing settings and see what happens. Personally I am trying to use Shoes in a small personal project. Nothing too fancy, but still a good opportunity to play around. I've enjoyed it so far, and recommend it... just not for anything too important.
Let me know if there are any complaints or questions.