The Shoes Manual

The Rules of Shoes

Time to stop guessing how Shoes works. Some of the tricky things will come back to haunt you. I've boiled down the central rules to Shoes. These are the things you MUST know to really make it all work.

These are general rules found throughout Shoes. While Shoes has an overall philosophy of simplicity and clarity, there are a few points that need to be studied and remembered.

Shoes Tricky Blocks

Okay, this is absolutely crucial. Shoes does a trick with blocks. This trick makes everything easier to read. But it also can make blocks harder to use once you're in deep.

Let's take a normal Ruby block:

 ary = ['potion', 'swords', 'shields']
 ary.each do |item|
   puts item
 end

In Shoes, these sorts of blocks work the same. This block above loops through the array and stores each object in the item variable. The item variable disappears (goes out of scope) when the block ends.

One other thing to keep in mind is that self stays the same inside normal Ruby blocks. Whatever self was before the call to each, it is the same inside the each block.

Both of these things are also true for most Shoes blocks.

 Shoes.app do
   stack do
     para "First"
     para "Second"
     para "Third"
   end
 end

Here we have two blocks. The first block is sent to Shoes.app. This app block changes self.

The other block is the stack block. That block does NOT change self.

For what reason does the app block change self? Let's start by spelling out that last example completely.

 Shoes.app do
   self.stack do
     self.para "First"
     self.para "Second"
     self.para "Third"
   end
 end

All of the selfs in the above example are the App object. Shoes uses Ruby's instance_eval to change self inside the app block. So the method calls to stack and para get sent to the app.

This also is why you can use instance variables throughout a Shoes app:

 Shoes.app do
   @s = stack do
     @p1 = para "First"
     @p2 = para "Second"
     @p3 = para "Third"
   end
 end

These instance variables will all end up inside the App object.

Whenever you create a new window, self is also changed. So, this means the window and dialog methods, in addition to Shoes.app.

 Shoes.app title: "MAIN" do
   para self
   button "Spawn" do
     window title: "CHILD" do
       para self
     end
   end
 end

Block Redirection

The stack block is a different story, though. It doesn't change self and it's basically a regular block.

But there's a trick: when you attach a stack and give it a block, the App object places that stack in its memory. The stack gets popped off when the block ends. So all drawing inside the block gets redirected from the App's top slot to the new stack.

So those three paras will get drawn on the stack, even though they actually get sent to the App object first.

 Shoes.app do
   stack do
     para "First"
     para "Second"
     para "Third"
   end
 end

A bit tricky, you see? This can bite you even if you know about it.

One way it'll get you is if you try to edit a stack somewhere else in your program, outside the app block.

Like let's say you pass around a stack object. And you have a class that edits that object.

 class Messenger
   def initialize(stack)
     @stack = stack
   end
   def add(msg)
     @stack.append do
       para msg
     end
   end
 end

So, let's assume you pass the stack object into your Messenger class when the app starts. And, later, when a message comes in, the add method gets used to append a paragraph to that stack. Should work, right?

Nope, it won't work. The para method won't be found. The App object isn't around any more. And it's the one with the para method.

Fortunately, each Shoes object has an app method that will let you reopen the App object so you can do somefurther editing.

 class Messenger
   def initialize(stack)
     @stack = stack
   end
   def add(msg)
     @stack.app do
       @stack.append do
         para msg
       end
     end
   end
 end

As you can imagine, the app object changes self to the App object.

So the rules here are:

1. Methods named "app" or which create new windows alter self to the App object.
(This is true for both Shoes.app and Slot.app, as well as window and dialog.)
2. Blocks attached to stacks, flows or any manipulation method (such as append) do not change self. Instead, they pop the slot on to the app's editing stack.

Careful With Fixed Heights

Fixed widths on slots are great so you can split the window into columns.

 Shoes.app do
   flow do
     stack width: 200 do
       caption "Column one"
       para "is 200 pixels wide"
     end
     stack width: -200 do
       caption "Column two"
       para "is 100% minus 200 pixels wide"
     end
   end
 end

Fixed heights on slots should be less common. Usually you want your text and images to just flow down the window as far as they can. Height usually happens naturally.

The important thing here is that fixed heights actually force slots to behave differently. To be sure that the end of the slot is chopped off perfectly, the slot becomes a nested window. A new layer is created by the operating system to keep the slot in a fixed square.

One difference between normal slots and nested window slots is that the latter can have scrollbars.

 Shoes.app do
   stack width: 200, height: 200, scroll: true do
     background "#DFA"
     100.times do |i|
       para "Paragraph No. #{i}"
     end
   end
 end

These nested windows require more memory. They tax the application a bit more. So if you're experiencing some slowness with hundreds of fixed-height slots, try a different approach.

Image and Shape Blocks

Most beginners start littering the window with shapes. It's just easier to throw all your rectangles and ovals in a slot.

However, bear in mind that Shoes will create objects for all those shapes!

 Shoes.app do
   fill black(0.1)
   100.times do |i|
     oval i, i, i * 2
   end
 end

In this example, one-hundred Oval objects are created. This isn't too bad. But things would be slimmer if we made these into a single shape.

 Shoes.app do
   fill black(0.1)
   shape do
     100.times do |i|
       oval i, i, i * 2
     end
   end
 end

Oh, wait. The ovals aren't filled in this time! That's because the ovals have been combined into a single huge shape. And Shoes isn't sure where to fill in this case.

So you usually only want to combine into a single shape when you're dealing strictly with outlines.

Another option is to combine all those ovals into a single image.

 Shoes.app do
   fill black(0.1)
   image 300, 300 do
     100.times do |i|
       oval i, i, i * 2
     end
   end
 end

There we go! The ovals are all combined into a single 300 x 300 pixel image. In this case, storing that image in memory might be much bigger than having one-hundred ovals around. But when you're dealing with thousands of shapes, the image block can be cheaper.

The point is: it's easy to group shapes together into image or shape blocks, so give it a try if you're looking to gain some speed. Shape blocks particularly will save you some memory and speed.

UTF-8 Everywhere

Ruby itself isn't Unicode aware. And UTF-8 is a type of Unicode. (See Wikipedia for a full explanation of UTF-8.)

However, UTF-8 is common on the web. And lots of different platforms support it. So to cut down on the amount of conversion that Shoes has to do, Shoes expects all strings to be in UTF-8 format.

This is great because you can show a myriad of languages (Russian, Japanese, Spanish, English) using UTF-8 in Shoes. Just be sure that your text editor uses UTF-8!

To illustrate:

 Shoes.app do
   stack margin: 10 do
     @edit = edit_box width: 1.0 do
       @para.text = @edit.text
     end
     @para = para ""
   end
 end

This app will copy anything you paste into the edit box and display it in a Shoes paragraph. You can try copying some foreign text (such as Greek or Japanese) into this box to see how it displays.

This is a good test because it proves that the edit box gives back UTF-8 characters. And the paragraph can be set to any UTF-8 characters.

Important note: if some UTF-8 characters don't display for you, you will need to change the paragraph's font. This is especially common on OS X.

So, a good Japanese font on OS X is AppleGothic and on Windows is MS UI Gothic.

 Shoes.app do
   para "てすと (te-su-to)", font: case RUBY_PLATFORM
    when /mingw/; "MS UI Gothic"
    when /darwin/; "AppleGothic, Arial"
    else "Arial"
    end
 end

Again, anything which takes a string in Shoes will need a UTF-8 string. Edit boxes, edit lines, list boxes, window titles and text blocks all take UTF-8. If you give a string with bad characters in it, an error will show up in the console.

The Main App and Its Requires

NOTE: This rule is for Raisins. Policeman uses TOPLEVEL_BINDING. So, you can get main, Ruby top-level object, with the first snippet. Although you need to use Shoes::Para instead of Para outside Shoes.app block.

Each Shoes app is given a little room where it can create itself. You can create classes and set variables and they won't be seen by other Shoes programs. Each program runs inside its own anonymous class.

 main = self
 Shoes.app do
   para main.to_s
 end

This anonymous class is called (shoes) and it's just an empty, unnamed class. The Shoes module is mixed into this class (using include Shoes) so that you can use either Para or Shoes::Para when referring to the paragraph class.

The advantages of this approach are:

The second part is especially important to remember.

 class Storage; end
 Shoes.app do
   para Storage.new
 end

The Storage class will disappear once the app completes. Other apps aren't able to use the Storage class. And it can't be gotten to from files that are loaded using require.

When you require code, though, that code will stick around. It will be kept in the Ruby top-level environment.

So, the rule is: keep your temporary classes in the code with the app and keep your permanent classes in requires.

Next: Shoes